Come leggere un file CSV in un Datatable .NET


170

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

La normale funzionalità ADO.net lo consente?


21
In che modo questo è forse "fuori tema"? È una domanda specifica e 100 persone lo trovano utile
Ryan,

10
@Ryan: In verità ti dico ... I moderatori StackOverflow sono una covata di vipere. Mettiti alle spalle, moderatori StackOverflow!
Ronnie Overby,

Risposte:


89

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

Un parser generico portatile ed efficiente per file flat

È facile da configurare e facile da usare. Ti esorto a dare un'occhiata.


Eccellente davvero. Ha funzionato perfettamente per me fuori dalla scatola, senza nemmeno leggere la documentazione.
smirkingman

Funzionerà su file CSV in cui ogni riga può avere una struttura diversa? Ho un file di registro con diversi tipi di eventi registrati che dovrebbero essere separati in più tabelle.
Gonzobrains,

2
@gonzobrains - Probabilmente no; l'assunto di base di un file CSV è una struttura di dati rettangolare basata su un singolo set di intestazioni di colonna specificato nella prima riga. Sembra che siano dati più generici, delimitati da virgole e discriminati, che richiedono un "ETL" più sofisticato per analizzare il file in istanze di oggetti di vario tipo (che potrebbero includere righe di dati di diverse tabelle di dati).
KeithS

93

Ho usato il OleDbprovider. Tuttavia, presenta problemi se stai leggendo in righe con valori numerici ma desideri che vengano trattati come testo. Tuttavia, è possibile aggirare il problema creando un schema.inifile. 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;
    }
}

Grazie compagno. Questo mi ha aiutato. Avevo un file CSV in cui le virgole non erano solo separatori, erano ovunque all'interno di molti valori di colonne, quindi trovare una regex che avrebbe diviso la linea era un po 'impegnativo. OleDbProvider ha dedotto correttamente lo schema.
Galilyou,

L'implementazione ha senso ma come gestiamo le celle contenenti tipi di dati misti. Ad esempio, 40 ° C e così via?
Ricevuto il

GKED, se i dati che stai leggendo hanno sempre una serie prevista di colonne e tipi, puoi inserire nella stessa cartella un file shema.ini che informa le informazioni del provider OleDb sulle colonne. Ecco un collegamento a un articolo di Microsoft che fornisce dettagli su come strutturare il file. msdn.microsoft.com/en-us/library/…
Jim Scott

4
Mentre questa risposta funzionerà, lo sconsiglio vivamente. Si introduce una dipendenza esterna che può essere in conflitto con altre installazioni di Office sullo stesso computer (utilizzare Excel nell'ambiente di sviluppo locale?), A seconda delle versioni installate. Esistono pacchetti NuGet (ExcelDataReader, CsvHelper) che lo fanno in modo più efficiente e portatile.
A. Murray,

1
@ A.Murray - Cosa intendi esattamente? Questo utilizza il provider OleDb integrato in System.Data.dll. Non è necessario installare alcun "driver" aggiuntivo. E sarei scioccato al giorno d'oggi se su qualsiasi installazione di Windows non fosse installato il driver Jet di base. Questo è il CSV degli anni '90 ....
Paul Easter,

40

Ho deciso di utilizzare il lettore CSV di Sebastien Lorion .

Il suggerimento di Jay Riggs è anche un'ottima soluzione, ma non avevo solo bisogno di tutte le funzionalità che offre il parser generico di Andrew Rissing .

AGGIORNAMENTO 25/10/2010

Dopo aver usato il lettore CSV 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 ritengo siano ben formati.

Quindi sono passato al parser generico di Andrew Rissing e sembra che stia andando molto meglio.

AGGIORNAMENTO 22/09/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());
}

Sono d'accordo che il lettore CSV di Sebastien Lorien sia fantastico. Lo uso per l'elaborazione CSV pesante, ma ho anche usato Andrew's Rissing's per piccoli lavori e mi ha servito bene. Divertiti!
Jay Riggs,

Come posso usare queste classi per caricare CSV in DATATABLE?
Muflix,

Ho provato questo, ma la raccolta it.Current.Keys ritorna con "System.Linq.Enumerable + WhereSelectListIterator`2 [System.Int32, System.Char]" anziché il nome della colonna. Qualche idea sul perché?
user3658298

Puoi usare delimitatori multi carattere?
tira il

No, ma ho pensato di abilitarlo.
Ronnie Overby il

32

Ehi, funziona 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 qui la descrizione dell'immagine

Tabella dati importata inserisci qui la descrizione dell'immagine


7
Solo quando il 100% degli input è il più semplice dei file CSV (che potrebbe essere vero nel tuo caso).
Ronnie Overby

Hai ragione. dovresti usare codeproject.com/Articles/9258/A-Fast-CSV-Reader (Lorion dll) Ho provato a farlo bene.
Shivam Srivastava,

1
Vedi la mia risposta del 2009.
Ronnie Overby,

1
@ShivamSrivastava Ricevo l'errore nell'ultima riga ci sei, poi ti do altre informazioni di contatto
Sunil Acharya,

Sebbene non abbia usato esattamente questa versione, è stato basato su di essa che ho risolto il mio problema. Grazie. Funziona molto bene
nrod

13

Abbiamo sempre usato il driver Jet.OLEDB, fino a quando non abbiamo iniziato ad andare alle applicazioni a 64 bit. Microsoft non ha e non rilascerà un driver Jet a 64 bit. Ecco una semplice soluzione che abbiamo trovato che utilizza 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 usiamo principalmente per leggere file di configurazione personalizzati: la parte interessante dell'utilizzo dei file CSV è che possiamo 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);
}

8

questo è il codice che lo uso, ma le tue app devono funzionare con la versione 3.5 della rete

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<string> ReadAsLines(string filename)
        {
            using (StreamReader reader = new StreamReader(filename))
                while (!reader.EndOfStream)
                    yield return reader.ReadLine();
        }

Questo è praticamente quello che volevo presentare.
Capitano Kenpachi,

8

È possibile ottenerlo utilizzando la dll Microsoft.VisualBasic.FileIO.TextFieldParser 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;
        }

Non tentare di reinventare la ruota con l'elaborazione CSV. Ci sono così tante fantastiche alternative open source là fuori che sono molto robuste.
Mike Cole,

1
Grazie Brad, suggerimento utile in relazione a TextFieldParser per la gestione delle virgolette incorporate.
Mattpm

3
public class Csv
{
    public static DataTable DataSetGet(string filename, string separatorChar, out List<string> errors)
    {
        errors = new List<string>();
        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;
    }
}

3

Mi sono imbattuto in questo pezzo di codice che utilizza Linq e regex per analizzare un file CSV. L'articolo di riferimento ha ormai più di un anno e mezzo, ma non ha trovato un modo più semplice per analizzare un CSV usando Linq (e regex) di questo. L'avvertenza è che la regex applicata qui è per i file delimitati da virgole (rileverà le virgole tra virgolette!) E che potrebbe non prendere bene 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

3

L'opzione migliore che ho trovato, e risolve i problemi in cui potresti avere diverse versioni di Office installate, e anche problemi a 32/64-bit come menzionato Chuck Bevitt , è FileHelpers .

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

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

puoi dire cos'è CommonEngine? NuGet è uguale a NuGet.Core. Ho trovato solo NuGet.Core nei riferimenti
sindhu jampani

È necessario FileHelpers. Se hai NuGet, aggiungilo con NuGet. Altrimenti, aggiungilo come assemblaggio nel tuo progetto. CommonEngine fa parte di FileHelpers.
Neo

3

Per coloro che desiderano non utilizzare una libreria esterna e preferiscono non utilizzare OleDB, vedere l'esempio seguente. Tutto quello che ho trovato è stato OleDB, libreria esterna o semplicemente una divisione basata su una virgola! Nel mio caso OleDB non funzionava, quindi volevo qualcosa di diverso.

Ho trovato un articolo di MarkJ che faceva riferimento al metodo Microsoft.VisualBasic.FileIO.TextFieldParser come visto qui . L'articolo è scritto in VB e non restituisce un datatable, quindi vedi il mio esempio di seguito.

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

3

Risposta molto semplice: se non hai un csv complesso che può usare una semplice funzione split, funzionerà bene per l'importazione (nota che questo importa come stringhe, faccio conversioni di tipo di dati in seguito, 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 di stringa [] 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 tal caso, voglio fare un ciclo fino a quando 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;
    }

Bello, non hai ancora dichiarato le righe come stringa [].
Animal Style

@AnimalStyle hai ragione - aggiornato con metodo più robusto e righe dichiarate
Matt Farguson

3

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

Quindi questo risolve un problema di memoria giusto? Si tratta di elaborazione riga per riga e non persistente in memoria, quindi non dovrebbero esserci eccezioni? Mi piace il modo in cui questo viene elaborato ma File.ReadAllLines () non salva tutto in memoria? Penso che dovresti usare File.ReadLines () per evitare un enorme buffer di memoria? Questa è una buona risposta per la domanda che voglio solo conoscere i problemi di memoria.
DtechNet,

2

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 compilato, è possibile valutare le proprietà del datatable, come ColumnName, per sfruttare tutti i poteri degli oggetti dati ADO.Net.

In VS2008 puoi usare Linq per ottenere lo stesso effetto.

NOTA: questo può essere un duplicato di questa domanda SO.


2

Non posso resistere aggiungendo 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à su nomi di colonne duplicati
  • Gestisce le virgole nei dati
  • Gestisce qualsiasi delimitatore, non solo virgole (sebbene sia 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 i nomi di colonne duplicate da trovare come mia risposta in Come aggiungere numeri univoci a un elenco di stringhe

Ed ecco la BlankToNothingfunzione di aiuto:

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

2

Con Cinchoo ETL , una libreria open source, puoi facilmente convertire 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, consultare l' articolo codeproject .

Spero che sia d'aiuto.


2
    private static DataTable LoadCsvData(string refPath)
    {
        var cfg = new Configuration() { Delimiter = ",", HasHeaderRecord = true };
        var result = new DataTable();
        using (var sr = new StreamReader(refPath, Encoding.UTF8, false, 16384 * 2))
        {
            using (var rdr = new CsvReader(sr, cfg))
            using (var dataRdr = new CsvDataReader(rdr))
            {
                result.Load(dataRdr);
            }
        }
        return result;
    }

utilizzando: https://joshclose.github.io/CsvHelper/


Si noti che nella versione 13 è Configuration stato rinominato CsvConfiguration per evitare conflitti nello spazio dei nomi. Demo di questa risposta funzionante: dotnetfiddle.net/sdwc6i
dbc

2

Uso una libreria chiamata ExcelDataReader, la puoi trovare su NuGet. Assicurarsi di installare sia ExcelDataReader che l'estensione ExcelDataReader.DataSet (quest'ultima fornisce il metodo AsDataSet richiesto indicato di seguito).

Ho incapsulato tutto in una funzione, puoi copiarlo direttamente nel tuo codice. Dagli un percorso per il file CSV, ti dà un set di dati con una tabella.

public static DataSet GetDataSet(string filepath)
{
   var stream = File.OpenRead(filepath);

   try
   {
       var reader = ExcelReaderFactory.CreateCsvReader(stream, new ExcelReaderConfiguration()
       {
           LeaveOpen = false
       });

       var result = reader.AsDataSet(new ExcelDataSetConfiguration()
       {
           // Gets or sets a value indicating whether to set the DataColumn.DataType 
           // property in a second pass.
           UseColumnDataType = true,

           // Gets or sets a callback to determine whether to include the current sheet
           // in the DataSet. Called once per sheet before ConfigureDataTable.
           FilterSheet = (tableReader, sheetIndex) => true,

           // Gets or sets a callback to obtain configuration options for a DataTable. 
           ConfigureDataTable = (tableReader) => new ExcelDataTableConfiguration()
           {
               // Gets or sets a value indicating the prefix of generated column names.
               EmptyColumnNamePrefix = "Column",

               // Gets or sets a value indicating whether to use a row from the 
               // data as column names.
               UseHeaderRow = true,

               // Gets or sets a callback to determine which row is the header row. 
               // Only called when UseHeaderRow = true.
               ReadHeaderRow = (rowReader) =>
               {
                   // F.ex skip the first row and use the 2nd row as column headers:
                   //rowReader.Read();
               },

               // Gets or sets a callback to determine whether to include the 
               // current row in the DataTable.
               FilterRow = (rowReader) =>
               {
                   return true;
               },

               // Gets or sets a callback to determine whether to include the specific
               // column in the DataTable. Called once per column after reading the 
               // headers.
               FilterColumn = (rowReader, columnIndex) =>
               {
                   return true;
               }
           }
       });

       return result;
   }
   catch (Exception ex)
   {
       return null;
   }
   finally
   {
       stream.Close();
       stream.Dispose();
   }
}

È il 2020 e questa è un'ottima soluzione rispetto ad alcune delle risposte più vecchie qui. È ben confezionato e utilizza una libreria popolare e leggera di NuGet. Ed è flessibile: se il tuo CSV è in memoria, passalo semplicemente come percorso MemoryStreamanziché come file. La DataTable richiesta da OP viene facilmente estratta dal DataSet in questo modo:result.Tables[0]
Tawab Wakil

1

Solo condividendo questi metodi di estensione, spero che possa aiutare qualcuno.

public static List<string> ToCSV(this DataSet ds, char separator = '|')
{
    List<string> lResult = new List<string>();

    foreach (DataTable dt in ds.Tables)
    {
        StringBuilder sb = new StringBuilder();
        IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                          Select(column => column.ColumnName);
        sb.AppendLine(string.Join(separator.ToString(), columnNames));

        foreach (DataRow row in dt.Rows)
        {
            IEnumerable<string> fields = row.ItemArray.Select(field =>
              string.Concat("\"", field.ToString().Replace("\"", "\"\""), "\""));
            sb.AppendLine(string.Join(separator.ToString(), fields));
        }

        lResult.Add(sb.ToString());
    }
    return lResult;
}

public static DataSet CSVtoDataSet(this List<string> collectionCSV, char separator = '|')
{
    var ds = new DataSet();

    foreach (var csv in collectionCSV)
    {
        var dt = new DataTable();

        var readHeader = false;
        foreach (var line in csv.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
        {
            if (!readHeader)
            {
                foreach (var c in line.Split(separator))
                    dt.Columns.Add(c);
            }
            else
            {
                dt.Rows.Add(line.Split(separator));
            }
        }

        ds.Tables.Add(dt);
    }

    return ds;
}

0

Usa questa, una funzione risolve tutti i problemi di virgola e citazione:

public static DataTable CsvToDataTable(string strFilePath)
    {

        if (File.Exists(strFilePath))
        {

            string[] Lines;
            string CSVFilePathName = strFilePath;

            Lines = File.ReadAllLines(CSVFilePathName);
            while (Lines[0].EndsWith(","))
            {
                Lines[0] = Lines[0].Remove(Lines[0].Length - 1);
            }
            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], typeof(string));
            DataRow Row;
            int rowcount = 0;
            try
            {
                string[] ToBeContinued = new string[]{};
                bool lineToBeContinued = false;
                for (int i = 1; i < Lines.GetLength(0); i++)
                {
                    if (!Lines[i].Equals(""))
                    {
                        Fields = Lines[i].Split(new char[] { ',' });
                        string temp0 = string.Join("", Fields).Replace("\"\"", "");
                        int quaotCount0 = temp0.Count(c => c == '"');
                        if (Fields.GetLength(0) < Cols || lineToBeContinued || quaotCount0 % 2 != 0)
                        {
                            if (ToBeContinued.GetLength(0) > 0)
                            {
                                ToBeContinued[ToBeContinued.Length - 1] += "\n" + Fields[0];
                                Fields = Fields.Skip(1).ToArray();
                            }
                            string[] newArray = new string[ToBeContinued.Length + Fields.Length];
                            Array.Copy(ToBeContinued, newArray, ToBeContinued.Length);
                            Array.Copy(Fields, 0, newArray, ToBeContinued.Length, Fields.Length);
                            ToBeContinued = newArray;
                            string temp = string.Join("", ToBeContinued).Replace("\"\"", "");
                            int quaotCount = temp.Count(c => c == '"');
                            if (ToBeContinued.GetLength(0) >= Cols && quaotCount % 2 == 0 )
                            {
                                Fields = ToBeContinued;
                                ToBeContinued = new string[] { };
                                lineToBeContinued = false;
                            }
                            else
                            {
                                lineToBeContinued = true;
                                continue;
                            }
                        }

                        //modified by Teemo @2016 09 13
                        //handle ',' and '"'
                        //Deserialize CSV following Excel's rule:
                        // 1: If there is commas in a field, quote the field.
                        // 2: Two consecutive quotes indicate a user's quote.

                        List<int> singleLeftquota = new List<int>();
                        List<int> singleRightquota = new List<int>();

                        //combine fileds if number of commas match
                        if (Fields.GetLength(0) > Cols) 
                        {
                            bool lastSingleQuoteIsLeft = true;
                            for (int j = 0; j < Fields.GetLength(0); j++)
                            {
                                bool leftOddquota = false;
                                bool rightOddquota = false;
                                if (Fields[j].StartsWith("\"")) 
                                {
                                    int numberOfConsecutiveQuotes = 0;
                                    foreach (char c in Fields[j]) //start with how many "
                                    {
                                        if (c == '"')
                                        {
                                            numberOfConsecutiveQuotes++;
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }
                                    if (numberOfConsecutiveQuotes % 2 == 1)//start with odd number of quotes indicate system quote
                                    {
                                        leftOddquota = true;
                                    }
                                }

                                if (Fields[j].EndsWith("\""))
                                {
                                    int numberOfConsecutiveQuotes = 0;
                                    for (int jj = Fields[j].Length - 1; jj >= 0; jj--)
                                    {
                                        if (Fields[j].Substring(jj,1) == "\"") // end with how many "
                                        {
                                            numberOfConsecutiveQuotes++;
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }

                                    if (numberOfConsecutiveQuotes % 2 == 1)//end with odd number of quotes indicate system quote
                                    {
                                        rightOddquota = true;
                                    }
                                }
                                if (leftOddquota && !rightOddquota)
                                {
                                    singleLeftquota.Add(j);
                                    lastSingleQuoteIsLeft = true;
                                }
                                else if (!leftOddquota && rightOddquota)
                                {
                                    singleRightquota.Add(j);
                                    lastSingleQuoteIsLeft = false;
                                }
                                else if (Fields[j] == "\"") //only one quota in a field
                                {
                                    if (lastSingleQuoteIsLeft)
                                    {
                                        singleRightquota.Add(j);
                                    }
                                    else
                                    {
                                        singleLeftquota.Add(j);
                                    }
                                }
                            }
                            if (singleLeftquota.Count == singleRightquota.Count)
                            {
                                int insideCommas = 0;
                                for (int indexN = 0; indexN < singleLeftquota.Count; indexN++)
                                {
                                    insideCommas += singleRightquota[indexN] - singleLeftquota[indexN];
                                }
                                if (Fields.GetLength(0) - Cols >= insideCommas) //probabaly matched
                                {
                                    int validFildsCount = insideCommas + Cols; //(Fields.GetLength(0) - insideCommas) may be exceed the Cols
                                    String[] temp = new String[validFildsCount];
                                    int totalOffSet = 0;
                                    for (int iii = 0; iii < validFildsCount - totalOffSet; iii++)
                                    {
                                        bool combine = false;
                                        int storedIndex = 0;
                                        for (int iInLeft = 0; iInLeft < singleLeftquota.Count; iInLeft++)
                                        {
                                            if (iii + totalOffSet == singleLeftquota[iInLeft])
                                            {
                                                combine = true;
                                                storedIndex = iInLeft;
                                                break;
                                            }
                                        }
                                        if (combine)
                                        {
                                            int offset = singleRightquota[storedIndex] - singleLeftquota[storedIndex];
                                            for (int combineI = 0; combineI <= offset; combineI++)
                                            {
                                                temp[iii] += Fields[iii + totalOffSet + combineI] + ",";
                                            }
                                            temp[iii] = temp[iii].Remove(temp[iii].Length - 1, 1);
                                            totalOffSet += offset;
                                        }
                                        else
                                        {
                                            temp[iii] = Fields[iii + totalOffSet];
                                        }
                                    }
                                    Fields = temp;
                                }
                            }
                        }
                        Row = dt.NewRow();
                        for (int f = 0; f < Cols; f++)
                        {
                            Fields[f] = Fields[f].Replace("\"\"", "\""); //Two consecutive quotes indicate a user's quote
                            if (Fields[f].StartsWith("\""))
                            {
                                if (Fields[f].EndsWith("\""))
                                {
                                    Fields[f] = Fields[f].Remove(0, 1);
                                    if (Fields[f].Length > 0)
                                    {
                                        Fields[f] = Fields[f].Remove(Fields[f].Length - 1, 1);
                                    }
                                }
                            }
                            Row[f] = Fields[f];
                        }
                        dt.Rows.Add(Row);
                        rowcount++;
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception( "row: " + (rowcount+2) + ", " + ex.Message);
            }
            //OleDbConnection connection = new OleDbConnection(string.Format(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}; Extended Properties=""text;HDR=Yes;FMT=Delimited"";", FilePath + FileName));
            //OleDbCommand command = new OleDbCommand("SELECT * FROM " + FileName, connection);
            //OleDbDataAdapter adapter = new OleDbDataAdapter(command);
            //DataTable dt = new DataTable();
            //adapter.Fill(dt);
            //adapter.Dispose();
            return dt;
        }
        else
            return null;

        //OleDbConnection connection = new OleDbConnection(string.Format(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}; Extended Properties=""text;HDR=Yes;FMT=Delimited"";", strFilePath));
        //OleDbCommand command = new OleDbCommand("SELECT * FROM " + strFileName, connection);
        //OleDbDataAdapter adapter = new OleDbDataAdapter(command);
        //DataTable dt = new DataTable();
        //adapter.Fill(dt);
        //return dt;
    }

0
 Public Function ReadCsvFileToDataTable(strFilePath As String) As DataTable
    Dim dtCsv As DataTable = New DataTable()
    Dim Fulltext As String
    Using sr As StreamReader = New StreamReader(strFilePath)
        While Not sr.EndOfStream
            Fulltext = sr.ReadToEnd().ToString()
            Dim rows As String() = Fulltext.Split(vbLf)
            For i As Integer = 0 To rows.Count() - 1 - 1
                Dim rowValues As String() = rows(i).Split(","c)
                If True Then
                    If i = 0 Then
                        For j As Integer = 0 To rowValues.Count() - 1
                            dtCsv.Columns.Add(rowValues(j))
                        Next
                    Else
                        Dim dr As DataRow = dtCsv.NewRow()
                        For k As Integer = 0 To rowValues.Count() - 1
                            dr(k) = rowValues(k).ToString()
                        Next
                        dtCsv.Rows.Add(dr)
                    End If
                End If
            Next
        End While
    End Using
    Return dtCsv
End Function

0

Di recente ho scritto un parser CSV per .NET che sostengo sia attualmente il più veloce disponibile come pacchetto nuget : Sylvan.Data.Csv .

L'utilizzo di questa libreria per caricare a DataTableè estremamente semplice.

using var tr = File.OpenText("data.csv");
using var dr = CsvDataReader.Create(tr);
var dt = new DataTable();
dt.Load(dr);

Supponendo che il tuo file sia un file separato da virgole standard con le intestazioni, questo è tutto ciò di cui hai bisogno. Ci sono anche opzioni per consentire la lettura di file senza intestazioni e l'uso di delimitatori alternativi, ecc.

È anche possibile fornire uno schema personalizzato per il file CSV in modo che le colonne possano essere trattate come qualcosa di diverso dai stringvalori. Ciò consentirà DataTabledi caricare le colonne con valori che possono essere più facili da lavorare, poiché non sarà necessario forzarli quando si accede ad essi.

var schema = new TypedCsvSchema();
schema.Add(0, typeof(int));
schema.Add(1, typeof(string));
schema.Add(2, typeof(double?));
schema.Add(3, typeof(DateTime));
schema.Add(4, typeof(DateTime?));

var options = new CsvDataReaderOptions { 
    Schema = schema 
};

using var tr = GetData();
using var dr = CsvDataReader.Create(tr, options);

TypedCsvSchemaè un'implementazione di ICsvSchemaProvidercui fornisce un modo semplice per definire i tipi di colonne. Tuttavia, è anche possibile fornire una personalizzazione ICsvSchemaProviderquando si desidera fornire più metadati, come unicità o dimensione della colonna vincolata, ecc.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.