Come leggere un file CSV in un Datatable .NET

Come posso caricare un file CSV in un System.Data.DataTable , creando il datatable basato sul file CSV?

La normale funzionalità di ADO.net consente questo?

Ecco una class eccellente che copierà i dati CSV in un datatable utilizzando la struttura dei dati per creare il DataTable:

Un parser generico portatile ed efficiente per file flat

È facile da configurare e facile da usare. Vi esorto a dare un’occhiata.

Ho utilizzato il provider OleDb . Tuttavia, ha problemi se stai leggendo in righe che hanno valori numerici ma vuoi che vengano trattati come testo. Tuttavia, è ansible schema.ini problema creando un file schema.ini . Ecco il mio metodo che ho usato:

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

Ho deciso di usare il lettore Csv di Sebastien Lorion .

Anche il suggerimento di Jay Riggs è un’ottima soluzione, ma non ho proprio bisogno di tutte le funzionalità offerte da Generic Parser di Andrew Rissing .

AGGIORNAMENTO 25/10/2010

Dopo aver utilizzato il Csv Reader di Sebastien Lorion nel mio progetto per quasi un anno e mezzo, ho scoperto che genera eccezioni durante l’analisi di alcuni file CSV che credo siano ben formati.

Quindi, sono passato a Generic Parser di Andrew Rissing e sembra che stia andando molto meglio.

AGGIORNAMENTO 9/22/2014

In questi giorni, utilizzo principalmente questo metodo di estensione per leggere il testo delimitato:

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

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

AGGIORNAMENTO 20/2/2015

Esempio:

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

Ehi, sta lavorando 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; } 

Immagine CSV inserisci la descrizione dell'immagine qui

Tabella dati importata inserisci la descrizione dell'immagine qui

Abbiamo sempre usato il driver Jet.OLEDB, fino a quando abbiamo iniziato a utilizzare applicazioni a 64 bit. Microsoft non ha e non rilascerà un driver Jet a 64 bit. Ecco una soluzione semplice che è stata creata utilizzando File.ReadAllLines e String.Split per leggere e analizzare il file CSV e caricare manualmente un DataTable. Come notato sopra, NON gestisce la situazione in cui uno dei valori della colonna contiene una virgola. Lo utilizziamo principalmente per la lettura di file di configurazione personalizzati: la parte migliore sull’utilizzo dei file CSV è la possibilità di modificarli in 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); } 

questo è il codice che uso ma le tue app devono essere eseguite con net versione 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; } 

Mi sono imbattuto in questo pezzo di codice che usa Linq e regex per analizzare un file CSV. L’articolo di riferimento ora ha più di un anno e mezzo, ma non ha trovato un modo più ordinato di analizzare un CSV usando Linq (e regex) di questo. L’avvertenza è la regex applicata qui è per i file delimitati da virgola (rileverà le virgole tra virgolette!) E che potrebbe non essere buona per le intestazioni, ma c’è un modo per superarle). Prendi un picco:

 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 

Puoi ottenerlo utilizzando Microsoft.VisualBasic.FileIO.TextFieldParser dll in 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; } 

L’opzione migliore che ho trovato, e risolve i problemi in cui si possono avere diverse versioni di Office installate, e anche i problemi 32/64-bit come Chuck Bevitt menzionati , è FileHelpers .

Può essere aggiunto ai riferimenti del progetto usando NuGet e fornisce una soluzione one-liner:

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

Per quelli di voi che desiderano non utilizzare una libreria esterna e preferiscono non utilizzare OleDB, vedere l’esempio di seguito. Tutto quello che ho trovato era OleDB, libreria esterna o semplicemente suddivisione basata su una virgola! Per il mio caso, OleDB non funzionava, quindi volevo qualcosa di diverso.

Ho trovato un articolo di MarkJ che fa riferimento al metodo Microsoft.VisualBasic.FileIO.TextFieldParser come si vede qui . L’articolo è scritto in VB e non restituisce un datatable, quindi vedi il mio esempio qui sotto.

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

Risposta molto semplice: se non si dispone di un csv complesso che può utilizzare una semplice funzione di suddivisione, questo funzionerà bene per l’importazione (si noti che questo importa come stringhe, io faccio le conversioni dei tipi di dati in un secondo momento, se necessario)

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

Il mio metodo se sto importando una tabella con un separatore string [] e gestisce il problema in cui la riga corrente che sto leggendo potrebbe essere passata alla riga successiva nel file csv o di testo < - IN quale caso voglio loop finché non ottengo al numero totale di righe nella prima riga (colonne)

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

Modificato da Mr ChuckBevitt

Soluzione di lavoro:

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

Ecco una soluzione che utilizza il driver di testo ODBC di 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 volta compilati, è ansible valutare le proprietà del datatable, come ColumnName, per utilizzare tutte le potenzialità degli oggetti dati ADO.Net.

In VS2008 è ansible utilizzare Linq per ottenere lo stesso effetto.

NOTA: questo potrebbe essere un duplicato di questa domanda 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; } } 

Non posso resistere ad aggiungere il mio giro a questo. Questo è molto meglio e più compatto di quello che ho usato in passato.

Questa soluzione:

  • Non dipende da un driver di database o da una libreria di terze parti.
  • Non fallirà con nomi di colonne duplicati
  • Gestisce le virgole nei dati
  • Gestisce qualsiasi delimitatore, non solo virgole (anche se questo è l’impostazione predefinita)

Ecco cosa mi è venuto in mente:

  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 

Dipende da un metodo di estensione ( Unique ) per gestire nomi di colonne duplicati da trovare come risposta in Come aggiungere numeri univoci a un elenco di stringhe

Ed ecco la funzione di aiuto di 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 libreria open source, puoi facilmente convertire il file CSV in DataTable con poche righe di codice.

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

Per ulteriori informazioni, visitare l’articolo codeproject.

Spero che sia d’aiuto.