Cómo leer un archivo CSV en .NET Datatable

¿Cómo puedo cargar un archivo CSV en una System.Data.DataTable , creando la tabla de datos basada en el archivo CSV?

¿La funcionalidad regular de ADO.net lo permite?

Aquí hay una clase excelente que copiará los datos CSV en una tabla de datos usando la estructura de los datos para crear la DataTable:

Un analizador genérico portátil y eficiente para archivos planos

Es fácil de configurar y fácil de usar. Te pido que eches un vistazo.

He estado usando el proveedor OleDb . Sin embargo, tiene problemas si está leyendo en filas que tienen valores numéricos pero quiere que se traten como texto. Sin embargo, puede evitar ese problema creando un archivo schema.ini . Aquí está mi método que utilicé:

 // using System.Data; // using System.Data.OleDb; // using System.Globalization; // using System.IO; static DataTable GetDataTableFromCsv(string path, bool isFirstRowHeader) { string header = isFirstRowHeader ? "Yes" : "No"; string pathOnly = Path.GetDirectoryName(path); string fileName = Path.GetFileName(path); string sql = @"SELECT * FROM [" + fileName + "]"; using(OleDbConnection connection = new OleDbConnection( @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathOnly + ";Extended Properties=\"Text;HDR=" + header + "\"")) using(OleDbCommand command = new OleDbCommand(sql, connection)) using(OleDbDataAdapter adapter = new OleDbDataAdapter(command)) { DataTable dataTable = new DataTable(); dataTable.Locale = CultureInfo.CurrentCulture; adapter.Fill(dataTable); return dataTable; } } 

Decidí usar el Csv Reader de Sebastien Lorion .

La sugerencia de Jay Riggs también es una gran solución, pero simplemente no necesitaba todas las funciones que proporciona el analizador genérico de Andrew Rissing .

ACTUALIZACIÓN 25/10/2010

Después de usar Csv Reader de Sebastien Lorion en mi proyecto durante casi un año y medio, descubrí que arroja excepciones al analizar algunos archivos CSV que creo que están bien formados.

Entonces, cambié al analizador genérico de Andrew Rissing y parece estar mucho mejor.

ACTUALIZACIÓN 22/9/2014

Estos días, utilizo principalmente este método de extensión para leer texto delimitado:

https://github.com/Core-Techs/Common/blob/master/CoreTechs.Common/Text/DelimitedTextExtensions.cs#L22

https://www.nuget.org/packages/CoreTechs.Common/

ACTUALIZACIÓN 20/02/2015

Ejemplo:

 var csv = @"Name, Age Ronnie, 30 Mark, 40 Ace, 50"; TextReader reader = new StringReader(csv); var table = new DataTable(); using(var it = reader.ReadCsvWithHeader().GetEnumerator()) { if (!it.MoveNext()) return; foreach (var k in it.Current.Keys) table.Columns.Add(k); do { var row = table.NewRow(); foreach (var k in it.Current.Keys) row[k] = it.Current[k]; table.Rows.Add(row); } while (it.MoveNext()); } 

Oye, está funcionando al 100%

  public static DataTable ConvertCSVtoDataTable(string strFilePath) { DataTable dt = new DataTable(); using (StreamReader sr = new StreamReader(strFilePath)) { string[] headers = sr.ReadLine().Split(','); foreach (string header in headers) { dt.Columns.Add(header); } while (!sr.EndOfStream) { string[] rows = sr.ReadLine().Split(','); DataRow dr = dt.NewRow(); for (int i = 0; i < headers.Length; i++) { dr[i] = rows[i]; } dt.Rows.Add(dr); } } return dt; } 

Imagen CSV enter image description here

Tabla de datos importada enter image description here

Siempre usamos el controlador Jet.OLEDB, hasta que comenzamos a usar aplicaciones de 64 bits. Microsoft no ha lanzado ni lanzará un controlador Jet de 64 bits. Aquí hay una solución simple que surgió que usa File.ReadAllLines y String.Split para leer y analizar el archivo CSV y cargar manualmente una DataTable. Como se señaló anteriormente, NO maneja la situación en la que uno de los valores de columna contiene una coma. Usamos esto principalmente para leer archivos de configuración personalizados. Lo bueno de usar archivos CSV es que podemos editarlos en Excel.

 string CSVFilePathName = @"C:\test.csv"; string[] Lines = File.ReadAllLines(CSVFilePathName); string[] Fields; Fields = Lines[0].Split(new char[] { ',' }); int Cols = Fields.GetLength(0); DataTable dt = new DataTable(); //1st row must be column names; force lower case to ensure matching later on. for (int i = 0; i < Cols; i++) dt.Columns.Add(Fields[i].ToLower(), typeof(string)); DataRow Row; for (int i = 1; i < Lines.GetLength(0); i++) { Fields = Lines[i].Split(new char[] { ',' }); Row = dt.NewRow(); for (int f = 0; f < Cols; f++) Row[f] = Fields[f]; dt.Rows.Add(Row); } 

este es el código que uso, pero tus aplicaciones deben ejecutarse con la versión de red 3.5

 private void txtRead_Click(object sender, EventArgs e) { // var filename = @"d:\shiptest.txt"; openFileDialog1.InitialDirectory = "d:\\"; openFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*"; DialogResult result = openFileDialog1.ShowDialog(); if (result == DialogResult.OK) { if (openFileDialog1.FileName != "") { var reader = ReadAsLines(openFileDialog1.FileName); var data = new DataTable(); //this assume the first record is filled with the column names var headers = reader.First().Split(','); foreach (var header in headers) { data.Columns.Add(header); } var records = reader.Skip(1); foreach (var record in records) { data.Rows.Add(record.Split(',')); } dgList.DataSource = data; } } } static IEnumerable ReadAsLines(string filename) { using (StreamReader reader = new StreamReader(filename)) while (!reader.EndOfStream) yield return reader.ReadLine(); } 
 public DataTable CsvFileToDatatable(string path, bool IsFirstRowHeader)//here Path is root of file and IsFirstRowHeader is header is there or not { string header = "No"; string sql = string.Empty; DataTable dataTable = null; string pathOnly = string.Empty; string fileName = string.Empty; try { pathOnly = Path.GetDirectoryName(path); fileName = Path.GetFileName(path); sql = @"SELECT * FROM [" + fileName + "]"; if (IsFirstRowHeader) { header = "Yes"; } using (OleDbConnection connection = new OleDbConnection( @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathOnly + ";Extended Properties=\"Text;HDR=" + header + "\"")) { using (OleDbCommand command = new OleDbCommand(sql, connection)) { using (OleDbDataAdapter adapter = new OleDbDataAdapter(command)) { dataTable = new DataTable(); dataTable.Locale = CultureInfo.CurrentCulture; adapter.Fill(dataTable); } } } } finally { } return dataTable; } 

Encontré este fragmento de código que usa Linq y regex para analizar un archivo CSV. El artículo de referencia ahora tiene más de un año y medio de antigüedad, pero no se ha encontrado una manera más ordenada de analizar un CSV usando Linq (y regex) que esto. La advertencia es que la expresión regular que se aplica aquí es para archivos delimitados por comas (¡detectará comas dentro de comillas!) Y que puede no llevar bien a los encabezados, pero hay una forma de superarlos). Toma un pico:

 Dim lines As String() = System.IO.File.ReadAllLines(strCustomerFile) Dim pattern As String = ",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" Dim r As System.Text.RegularExpressions.Regex = New System.Text.RegularExpressions.Regex(pattern) Dim custs = From line In lines _ Let data = r.Split(line) _ Select New With {.custnmbr = data(0), _ .custname = data(1)} For Each cust In custs strCUSTNMBR = Replace(cust.custnmbr, Chr(34), "") strCUSTNAME = Replace(cust.custname, Chr(34), "") Next 

Puede lograrlo utilizando Microsoft.VisualBasic.FileIO.TextFieldParser dll en C #

 static void Main() { string csv_file_path=@"C:\Users\Administrator\Desktop\test.csv"; DataTable csvData = GetDataTabletFromCSVFile(csv_file_path); Console.WriteLine("Rows count:" + csvData.Rows.Count); Console.ReadLine(); } private static DataTable GetDataTabletFromCSVFile(string csv_file_path) { DataTable csvData = new DataTable(); try { using(TextFieldParser csvReader = new TextFieldParser(csv_file_path)) { csvReader.SetDelimiters(new string[] { "," }); csvReader.HasFieldsEnclosedInQuotes = true; string[] colFields = csvReader.ReadFields(); foreach (string column in colFields) { DataColumn datecolumn = new DataColumn(column); datecolumn.AllowDBNull = true; csvData.Columns.Add(datecolumn); } while (!csvReader.EndOfData) { string[] fieldData = csvReader.ReadFields(); //Making empty value as null for (int i = 0; i < fieldData.Length; i++) { if (fieldData[i] == "") { fieldData[i] = null; } } csvData.Rows.Add(fieldData); } } } catch (Exception ex) { } return csvData; } 

La mejor opción que he encontrado, y resuelve los problemas en los que puede tener diferentes versiones de Office instaladas, y también problemas de 32/64 bits como lo menciona Chuck Bevitt , es FileHelpers .

Se puede agregar a las referencias de su proyecto usando NuGet y proporciona una solución de una sola línea:

 CommonEngine.CsvToDataTable(path, "ImportRecord", ',', true); 

Para aquellos de ustedes que desean no utilizar una biblioteca externa, y prefieren no usar OleDB, vea el ejemplo a continuación. Todo lo que encontré fue OleDB, una biblioteca externa o simplemente dividir en una coma. Para mi caso, OleDB no funcionaba, así que quería algo diferente.

Encontré un artículo de MarkJ que hacía referencia al método Microsoft.VisualBasic.FileIO.TextFieldParser como se ve aquí . El artículo está escrito en VB y no devuelve una tabla de datos, por lo que veré mi ejemplo a continuación.

 public static DataTable LoadCSV(string path, bool hasHeader) { DataTable dt = new DataTable(); using (var MyReader = new Microsoft.VisualBasic.FileIO.TextFieldParser(path)) { MyReader.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited; MyReader.Delimiters = new String[] { "," }; string[] currentRow; //'Loop through all of the fields in the file. //'If any lines are corrupt, report an error and continue parsing. bool firstRow = true; while (!MyReader.EndOfData) { try { currentRow = MyReader.ReadFields(); //Add the header columns if (hasHeader && firstRow) { foreach (string c in currentRow) { dt.Columns.Add(c, typeof(string)); } firstRow = false; continue; } //Create a new row DataRow dr = dt.NewRow(); dt.Rows.Add(dr); //Loop thru the current line and fill the data out for(int c = 0; c < currentRow.Count(); c++) { dr[c] = currentRow[c]; } } catch (Microsoft.VisualBasic.FileIO.MalformedLineException ex) { //Handle the exception here } } } return dt; } 

Respuesta muy básica: si no tienes un csv complejo que pueda usar una función de división simple, esto funcionará bien para la importación (ten en cuenta que esto importa como cadenas, luego hago conversiones de tipos de datos si es necesario)

  private DataTable csvToDataTable(string fileName, char splitCharacter) { StreamReader sr = new StreamReader(fileName); string myStringRow = sr.ReadLine(); var rows = myStringRow.Split(splitCharacter); DataTable CsvData = new DataTable(); foreach (string column in rows) { //creates the columns of new datatable based on first row of csv CsvData.Columns.Add(column); } myStringRow = sr.ReadLine(); while (myStringRow != null) { //runs until string reader returns null and adds rows to dt rows = myStringRow.Split(splitCharacter); CsvData.Rows.Add(rows); myStringRow = sr.ReadLine(); } sr.Close(); sr.Dispose(); return CsvData; } 

Mi método si estoy importando una tabla con un separador de cadena [] y manejo el problema donde la línea actual que estoy leyendo puede haber ido a la siguiente línea en el archivo csv o de texto <- EN cuyo caso quiero hacer un bucle hasta obtener al número total de líneas en la primera fila (columnas)

 public static DataTable ImportCSV(string fullPath, string[] sepString) { DataTable dt = new DataTable(); using (StreamReader sr = new StreamReader(fullPath)) { //stream uses using statement because it implements iDisposable string firstLine = sr.ReadLine(); var headers = firstLine.Split(sepString, StringSplitOptions.None); foreach (var header in headers) { //create column headers dt.Columns.Add(header); } int columnInterval = headers.Count(); string newLine = sr.ReadLine(); while (newLine != null) { //loop adds each row to the datatable var fields = newLine.Split(sepString, StringSplitOptions.None); // csv delimiter var currentLength = fields.Count(); if (currentLength < columnInterval) { while (currentLength < columnInterval) { //if the count of items in the row is less than the column row go to next line until count matches column number total newLine += sr.ReadLine(); currentLength = newLine.Split(sepString, StringSplitOptions.None).Count(); } fields = newLine.Split(sepString, StringSplitOptions.None); } if (currentLength > columnInterval) { //ideally never executes - but if csv row has too many separators, line is skipped newLine = sr.ReadLine(); continue; } dt.Rows.Add(fields); newLine = sr.ReadLine(); } sr.Close(); } return dt; } 

Modificado de Mr ChuckBevitt

Solución de trabajo:

 string CSVFilePathName = APP_PATH + "Facilities.csv"; string[] Lines = File.ReadAllLines(CSVFilePathName); string[] Fields; Fields = Lines[0].Split(new char[] { ',' }); int Cols = Fields.GetLength(0); DataTable dt = new DataTable(); //1st row must be column names; force lower case to ensure matching later on. for (int i = 0; i < Cols-1; i++) dt.Columns.Add(Fields[i].ToLower(), typeof(string)); DataRow Row; for (int i = 0; i < Lines.GetLength(0)-1; i++) { Fields = Lines[i].Split(new char[] { ',' }); Row = dt.NewRow(); for (int f = 0; f < Cols-1; f++) Row[f] = Fields[f]; dt.Rows.Add(Row); } 

Aquí hay una solución que usa el controlador de texto ODBC de ADO.Net:

 Dim csvFileFolder As String = "C:\YourFileFolder" Dim csvFileName As String = "YourFile.csv" 'Note that the folder is specified in the connection string, 'not the file. That's specified in the SELECT query, later. Dim connString As String = "Driver={Microsoft Text Driver (*.txt; *.csv)};Dbq=" _ & csvFileFolder & ";Extended Properties=""Text;HDR=No;FMT=Delimited""" Dim conn As New Odbc.OdbcConnection(connString) 'Open a data adapter, specifying the file name to load Dim da As New Odbc.OdbcDataAdapter("SELECT * FROM [" & csvFileName & "]", conn) 'Then fill a data table, which can be bound to a grid Dim dt As New DataTableda.Fill(dt) grdCSVData.DataSource = dt 

Una vez completado, puede valorar las propiedades de la tabla de datos, como ColumnName, para aprovechar todas las potencias de los objetos de datos ADO.Net.

En VS2008 puede usar Linq para lograr el mismo efecto.

NOTA: Esto puede ser un duplicado de esta pregunta SO.

 public class Csv { public static DataTable DataSetGet(string filename, string separatorChar, out List errors) { errors = new List(); var table = new DataTable("StringLocalization"); using (var sr = new StreamReader(filename, Encoding.Default)) { string line; var i = 0; while (sr.Peek() >= 0) { try { line = sr.ReadLine(); if (string.IsNullOrEmpty(line)) continue; var values = line.Split(new[] {separatorChar}, StringSplitOptions.None); var row = table.NewRow(); for (var colNum = 0; colNum < values.Length; colNum++) { var value = values[colNum]; if (i == 0) { table.Columns.Add(value, typeof (String)); } else { row[table.Columns[colNum]] = value; } } if (i != 0) table.Rows.Add(row); } catch(Exception ex) { errors.Add(ex.Message); } i++; } } return table; } } 

No puedo resistirme a agregar mi propio giro a esto. Esto es mucho mejor y más compacto que lo que he usado en el pasado.

Esta solución:

  • No depende de un controlador de base de datos o una biblioteca de terceros.
  • No fallará en nombres de columna duplicados
  • Maneja comas en los datos
  • Maneja cualquier delimitador, no solo comas (aunque ese es el valor predeterminado)

Esto es lo que se me ocurrió:

  Public Function ToDataTable(FileName As String, Optional Delimiter As String = ",") As DataTable ToDataTable = New DataTable Using TextFieldParser As New Microsoft.VisualBasic.FileIO.TextFieldParser(FileName) With {.HasFieldsEnclosedInQuotes = True, .TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited, .TrimWhiteSpace = True} With TextFieldParser .SetDelimiters({Delimiter}) .ReadFields.ToList.Unique.ForEach(Sub(x) ToDataTable.Columns.Add(x)) ToDataTable.Columns.Cast(Of DataColumn).ToList.ForEach(Sub(x) x.AllowDBNull = True) Do Until .EndOfData ToDataTable.Rows.Add(.ReadFields.Select(Function(x) Text.BlankToNothing(x)).ToArray) Loop End With End Using End Function 

Depende de un método de extensión ( Unique ) manejar los nombres de columna duplicados para encontrarlos como mi respuesta en Cómo agregar números únicos a una lista de cadenas.

Y aquí está la función de ayuda BlankToNothing :

  Public Function BlankToNothing(ByVal Value As String) As Object If String.IsNullOrEmpty(Value) Then Return Nothing Return Value End Function 

Con Cinchoo ETL , una biblioteca de código abierto, puede convertir fácilmente archivos CSV a DataTable con pocas líneas de código.

 using (var p = new ChoCSVReader(** YOUR CSV FILE **) .WithFirstLineHeader() ) { var dt = p.AsDataTable(); } 

Para obtener más información, visite el artículo de codeproject.

Espero eso ayude.