Como ler um arquivo CSV em um .NET Datatable

Como posso carregar um arquivo CSV em um System.Data.DataTable , criando a tabela de dados com base no arquivo CSV?

A funcionalidade regular do ADO.net permite isso?

Aqui está uma excelente class que copia os dados CSV em uma tabela de dados usando a estrutura dos dados para criar a DataTable:

Um analisador genérico portátil e eficiente para arquivos simples

É fácil de configurar e fácil de usar. Eu recomendo que você dê uma olhada.

Eu tenho usado o provedor OleDb . No entanto, há problemas se você estiver lendo em linhas que possuem valores numéricos, mas deseja que sejam tratados como texto. No entanto, você pode contornar esse problema criando um arquivo schema.ini . Aqui está o meu método que usei:

 // 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; } } 

Eu decidi usar o Csv Reader de Sebastien Lorion .

A sugestão de Jay Riggs também é uma ótima solução, mas eu não precisava de todos os resources que o Generic Parser de Andrew Rissing fornece.

ATUALIZAÇÃO 25/10/2010

Depois de usar o Csv Reader de Sebastien Lorion em meu projeto por quase um ano e meio, descobri que ele lança exceções ao analisar alguns arquivos csv que acredito serem bem formados.

Então, eu mudei para o Generic Parser de Andrew Rissing e parece estar indo muito melhor.

ATUALIZAÇÃO 22/09/2014

Atualmente, uso principalmente esse método de extensão para ler textos delimitados:

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

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

ATUALIZAÇÃO 20/02/2015

Exemplo:

 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()); } 

Hey seu trabalho 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; } 

Imagem CSV insira a descrição da imagem aqui

Tabela de dados importada insira a descrição da imagem aqui

Nós sempre usamos o driver Jet.OLEDB, até começarmos a ir para aplicativos de 64 bits. A Microsoft não lançou e não lançará um driver Jet de 64 bits. Aqui está uma solução simples que surgiu com o uso de File.ReadAllLines e String.Split para ler e analisar o arquivo CSV e carregar manualmente um DataTable. Como mencionado acima, NÃO lida com a situação em que um dos valores da coluna contém uma vírgula. Usamos isso principalmente para ler arquivos de configuração personalizados – a parte interessante sobre o uso de arquivos CSV é que podemos editá-los no 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 é o código que eu uso, mas seus aplicativos devem ser executados com a versão 3.5 da net

 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; } 

Me deparei com este pedaço de código que usa o Linq e regex para analisar um arquivo CSV. O artigo de referência agora tem mais de um ano e meio, mas não encontrou uma maneira mais simples de analisar um CSV usando Linq (e regex) do que isso. A ressalva é que o regex aplicado aqui é para arquivos delimitados por vírgulas (irá detectar vírgulas dentro de aspas!) E que pode não ser bom para os headers, mas existe uma maneira de superá-los). Tome um 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 

Você pode conseguir isso usando Microsoft.VisualBasic.FileIO.TextFieldParser dll em 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; } 

A melhor opção que encontrei e resolve problemas em que você pode ter versões diferentes do Office instaladas, e também problemas de 32/64 bits, como o Chuck Bevitt mencionou , é o FileHelpers .

Ele pode ser adicionado às referências do seu projeto usando o NuGet e fornece uma solução de uma linha:

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

Para aqueles que não desejam usar uma biblioteca externa e preferem não usar o OleDB, veja o exemplo abaixo. Tudo o que encontrei foi o OleDB, biblioteca externa, ou simplesmente dividir com base em uma vírgula! No meu caso, o OleDB não estava funcionando, então queria algo diferente.

Eu encontrei um artigo por MarkJ que referencia o método Microsoft.VisualBasic.FileIO.TextFieldParser como visto aqui . O artigo está escrito em VB e não retorna uma tabela de dados, então veja meu exemplo abaixo.

 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; } 

Resposta muito básica: se você não tem um csv complexo que pode usar uma function de divisão simples, isso funcionará bem para importar (note que isso importa como strings, eu faço datatype de conversões mais tarde se eu precisar)

  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; } 

Meu método se eu estou importando uma tabela com uma string separa [] e lida com o problema onde a linha atual que eu estou lendo pode ter ido para a próxima linha no csv ou arquivo de texto <- EM caso em que eu quero loop até chegar para o número total de linhas na primeira linha (colunas)

 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 pelo Sr. ChuckBevitt

Solução de trabalho:

 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); } 

Aqui está uma solução que usa o driver de texto ODBC do 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 

Depois de preenchido, você pode valorizar as propriedades da tabela de dados, como ColumnName, para utilizar todos os poderes dos objects de dados ADO.Net.

No VS2008, você pode usar o Linq para obter o mesmo efeito.

NOTA: Esta pode ser uma duplicata desta questão 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; } } 

Não posso resistir a adicionar minha própria rotação para isso. Isso é muito melhor e mais compacto do que eu usei no passado.

Esta solução:

  • Não depende de um driver de database ou de uma biblioteca de terceiros.
  • Não falhará em nomes de coluna duplicados
  • Manipula vírgulas nos dados
  • Lida com qualquer delimitador, não apenas vírgulas (embora seja o padrão)

Aqui está o que eu criei:

  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 um método de extensão ( Unique ) para lidar com nomes de coluna duplicados a serem encontrados como minha resposta em Como append números exclusivos a uma lista de strings

E aqui está a function auxiliar do BlankToNothing :

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

Com o Cinchoo ETL – uma biblioteca de código aberto, você pode converter facilmente o arquivo CSV para DataTable com poucas linhas de código.

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

Para mais informações, visite o artigo codeproject.

Espero que ajude.