Lettura di file CSV utilizzando C #


169

Sto scrivendo una semplice applicazione di importazione e ho bisogno di leggere un file CSV, mostrare il risultato in DataGride mostrare righe danneggiate del file CSV in un'altra griglia. Ad esempio, mostra le linee che sono più brevi di 5 valori in un'altra griglia. Sto cercando di farlo in questo modo:

StreamReader sr = new StreamReader(FilePath);
importingData = new Account();
string line;
string[] row = new string [5];
while ((line = sr.ReadLine()) != null)
{
    row = line.Split(',');

    importingData.Add(new Transaction
    {
        Date = DateTime.Parse(row[0]),
        Reference = row[1],
        Description = row[2],
        Amount = decimal.Parse(row[3]),
        Category = (Category)Enum.Parse(typeof(Category), row[4])
    });
}

ma in questo caso è molto difficile operare su array. C'è un modo migliore per dividere i valori?


Grazie per la tua soluzione Prendi in considerazione l'idea di pubblicarlo come post di risposta, includendolo nella domanda non aiuta la sua leggibilità.
BartoszKP,

Risposte:


363

Non reinventare la ruota. Approfitta di ciò che è già in .NET BCL.

  • aggiungi un riferimento a Microsoft.VisualBasic(sì, dice VisualBasic ma funziona anche in C # - ricorda che alla fine è tutto solo IL)
  • usa la Microsoft.VisualBasic.FileIO.TextFieldParserclasse per analizzare il file CSV

Ecco il codice di esempio:

using (TextFieldParser parser = new TextFieldParser(@"c:\temp\test.csv"))
{
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    while (!parser.EndOfData) 
    {
        //Processing row
        string[] fields = parser.ReadFields();
        foreach (string field in fields) 
        {
            //TODO: Process field
        }
    }
}

Funziona benissimo per me nei miei progetti C #.

Ecco alcuni altri link / informazioni:


18
Vorrei davvero che ci fosse un modo che non usasse le librerie VB, ma questo ha funzionato perfettamente! Grazie!
gillonba,

5
+1: Ho appena rotto il lettore CSV Fast di lumenworks su un file da 53 Mb. Sembra che la memorizzazione nella cache della riga non sia riuscita dopo 43.000 righe e abbia confuso il buffer. Ho provato il VB TextFieldParsere ha fatto il trucco. Grazie
Gone Coding,

10
+1 Ottima risposta, poiché trovo che molte persone non sappiano che questa classe esiste. Una cosa che i futuri spettatori possono notare è che l'impostazione parser.TextFieldType = FieldType.Delimited;non è necessaria se chiami parser.SetDelimiters(",");, poiché il metodo imposta la TextFieldTypeproprietà per te.
Brian,

10
Dai un'occhiata anche a: dotnetperls.com/textfieldparser . TextFieldParser ha prestazioni peggiori rispetto a String.Split e StreamReader. Tuttavia, c'è una grande differenza tra string.Split e TextFieldParser. TextFieldParser gestisce strani casi come avere una virgola in una colonna: puoi nominare una colonna come "text with quote"", and comma"e puoi ottenere il valore corretto text with quote", and commainvece di valori separati in modo errato. Quindi potresti voler optare per String.Split se csv è molto semplice.
Yongwei Wu,

5
Si noti che potrebbe essere necessario aggiungere un riferimento a Microsoft.VisualBasic per utilizzare questo. Fare clic con il tasto destro del mouse sul progetto in Visual Studio, quindi selezionare Aggiungi> Riferimento e selezionare la casella per Microsoft.VisualBasic.
Derek Kurth,

37

La mia esperienza è che ci sono molti diversi formati CSV. Specialmente come gestiscono l'escaping di virgolette e delimitatori all'interno di un campo.

Queste sono le varianti in cui mi sono imbattuto:

  • le virgolette sono quotate e raddoppiate (excel) cioè 15 "-> field1," 15 "" ", field3
  • le virgolette non vengono modificate a meno che il campo non sia quotato per qualche altro motivo. cioè 15 "-> field1,15", fields3
  • le virgolette sono salvate con \. cioè 15 "-> field1," 15 \ "", field3
  • le virgolette non vengono affatto modificate (non è sempre possibile analizzare correttamente)
  • il delimitatore è citato (excel). cioè a, b -> campo1, "a, b", campo3
  • il delimitatore viene eliminato con \. cioè a, b -> campo1, a \, b, campo3

Ho provato molti dei parser CSV esistenti ma non ce n'è uno in grado di gestire le varianti in cui mi sono imbattuto. È anche difficile scoprire dalla documentazione quali varianti di escape supportano i parser.

Nei miei progetti ora uso VB TextFieldParser o uno splitter personalizzato.


1
Adoro questa risposta per i casi di test che hai fornito!
Matthew Rodatus,

2
Il problema principale è che la maggior parte delle implementazioni non si preoccupa dell'RFC 4180 che descrive il formato CSV e come evitare i delimitatori.
Jenny O'Reilly,

RFC-4180 è del 2005, che sembra ormai vecchio, ma ricorda: il framework .Net è stato introdotto per la prima volta nel 2001. Inoltre, le RFC non sono sempre standard ufficiali, e in questo caso non hanno lo stesso peso, diciamo , ISO-8601 o RFC-761.
Joel Coehoorn,

23

Consiglio CsvHelper di Nuget .

(L'aggiunta di un riferimento a Microsoft.VisualBasic non sembra giusto, non è solo brutta, probabilmente non è nemmeno multipiattaforma.)


2
È esattamente multipiattaforma come C #.
PRMan,

sbagliato, Microsoft.VisualBasic.dll in Linux proviene da fonti Mono, che dispone di un'implementazione diversa da quella di Microsoft e ci sono alcune cose che non sono implementati, per esempio: stackoverflow.com/questions/6644165/...
knocte

(Inoltre, il linguaggio VB non ha mai avuto alcun focus nelle aziende che sono state coinvolte nella creazione / sviluppo del progetto Mono, quindi è molto indietro in termini di sforzi, rispetto all'ecosistema / tooling C #.)
knocte

1
Avendo giocato con entrambi, aggiungerei che CsvHelperviene fornito con una riga integrata nel mapper di classe; consente variazioni nelle intestazioni delle colonne (se presenti) e persino apparentemente variazioni nell'ordine delle colonne (anche se non ho testato quest'ultimo da solo). Tutto sommato sembra molto più "di alto livello" di TextFieldParser.
David,

1
Sì, lo spazio dei nomi Microsoft.VisualBasic non è disponibile su .NET Core 2.1
N4ppeL il

13

A volte l'uso delle librerie è interessante quando non si desidera reinventare la ruota, ma in questo caso si può fare lo stesso lavoro con meno righe di codice e più facili da leggere rispetto all'utilizzo delle librerie. Ecco un approccio diverso che trovo molto facile da usare.

  1. In questo esempio, utilizzo StreamReader per leggere il file
  2. Regex per rilevare il delimitatore da ciascuna linea (e).
  3. Un array per raccogliere le colonne dall'indice 0 al n

using (StreamReader reader = new StreamReader(fileName))
    {
        string line; 

        while ((line = reader.ReadLine()) != null)
        {
            //Define pattern
            Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");

            //Separating columns to array
            string[] X = CSVParser.Split(line);

            /* Do something with X */
        }
    }

4
Sicuramente questo ha problemi con i dati che a sua volta contengono nuove righe?
Doogal

Ora i file di dati CSV non sono noti per contenere righe vuote tra i dati, ma se si dispone di una fonte che lo fa, in quel caso farei semplicemente un semplice test regex per rimuovere spazi bianchi o righe che non contengono nulla prima di eseguire il lettore. controlla qui per diversi esempi: stackoverflow.com/questions/7647716/…
Mana

1
Sicuramente un approccio basato sul carattere è più naturale per questo tipo di problema che per una regex. A seconda della presenza di virgolette, il comportamento dovrebbe essere diverso.
Casey,

6

CSV possono complicarsi reale veloce.

Usa qualcosa di robusto e ben testato:
FileHelpers: www.filehelpers.net

FileHelpers è una libreria .NET gratuita e facile da usare per importare / esportare dati da record a lunghezza fissa o delimitati in file, stringhe o flussi.


5
Penso che FileHelper stia cercando di fare molto in una volta sola. L'analisi dei file è un processo in 2 passaggi in cui prima dividi le linee in campi e poi analizzi i campi in dati. La combinazione delle funzioni rende difficile gestire cose come il dettaglio principale e il filtro di linea.
Adrianm,


4

Un altro di questo elenco, Cinchoo ETL - una libreria open source per leggere e scrivere file CSV

Per un esempio di file CSV di seguito

Id, Name
1, Tom
2, Mark

Puoi caricarli rapidamente usando la libreria come di seguito

using (var reader = new ChoCSVReader("test.csv").WithFirstLineHeader())
{
   foreach (dynamic item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

Se si dispone della classe POCO corrispondente al file CSV

public class Employee
{
   public int Id { get; set; }
   public string Name { get; set; }
}

Puoi usarlo per caricare il file CSV come di seguito

using (var reader = new ChoCSVReader<Employee>("test.csv").WithFirstLineHeader())
{
   foreach (var item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

Consulta gli articoli su CodeProject su come utilizzarlo.

Disclaimer: sono l'autore di questa biblioteca


Ciao, puoi caricare CSV nella tabella SQL? Non conosco prima l'intestazione nella tabella CSV. Basta specchiare cosa c'è in CSV al tavolo Sql
aggie

Si, puoi. si prega di consultare questo link stackoverflow.com/questions/20759302/...
RajN

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

        private static void WriteToDb(DataTable dt)
        {
            string connectionString =
                "Data Source=localhost;" +
                "Initial Catalog=Northwind;" +
                "Integrated Security=SSPI;";

            using (SqlConnection con = new SqlConnection(connectionString))
                {
                    using (SqlCommand cmd = new SqlCommand("spInsertTest", con))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;

                        cmd.Parameters.Add("@policyID", SqlDbType.Int).Value = 12;
                        cmd.Parameters.Add("@statecode", SqlDbType.VarChar).Value = "blagh2";
                        cmd.Parameters.Add("@county", SqlDbType.VarChar).Value = "blagh3";

                        con.Open();
                        cmd.ExecuteNonQuery();
                    }
                }

         }

da dove hai copiato questa soluzione?
MindRoasterMir

0

Prima di tutto devi capire cos'è CSV e come scriverlo.

  1. Ogni stringa successiva ( /r/n) è la riga successiva "tabella".
  2. Le celle "Tabella" sono separate da un simbolo delimitatore. I simboli più usati sono \to,
  3. Ogni cella può eventualmente contenere questo simbolo delimitatore (la cella deve iniziare con il simbolo delle virgolette e in questo caso termina con questo simbolo)
  4. Ogni cella può contenere /r/nsimboli (la cella deve iniziare con il simbolo delle virgolette e in questo caso termina con questo simbolo)

Il modo più semplice per utilizzare C # / Visual Basic con i file CSV è utilizzare la Microsoft.VisualBasiclibreria standard . Devi solo aggiungere il riferimento necessario e la seguente stringa alla tua classe:

using Microsoft.VisualBasic.FileIO;

Sì, puoi usarlo in C #, non ti preoccupare. Questa libreria può leggere file relativamente grandi e supporta tutte le regole necessarie, così sarai in grado di lavorare con tutti i file CSV.

Qualche tempo fa avevo scritto lezioni semplici per CSV in lettura / scrittura basate su questa libreria. Usando questa semplice classe sarai in grado di lavorare con CSV come con un array di 2 dimensioni. Puoi trovare la mia classe al seguente link: https://github.com/ukushu/DataExporter

Semplice esempio di utilizzo:

Csv csv = new Csv("\t");//delimiter symbol

csv.FileOpen("c:\\file1.csv");

var row1Cell6Value = csv.Rows[0][5];

csv.AddRow("asdf","asdffffff","5")

csv.FileSave("c:\\file2.csv");

0

Per completare le risposte precedenti, potrebbe essere necessario un insieme di oggetti dal suo file CSV, analizzato dal metodo TextFieldParsero string.Split, e quindi ogni riga convertita in un oggetto tramite Reflection. Ovviamente devi prima definire una classe che corrisponda alle linee del file CSV.

Ho usato il semplice serializzatore CSV di Michael Kropat trovato qui: classe generica in CSV (tutte le proprietà) e riutilizzato i suoi metodi per ottenere i campi e le proprietà della classe desiderata.

Deserializzo il mio file CSV con il seguente metodo:

public static IEnumerable<T> ReadCsvFileTextFieldParser<T>(string fileFullPath, string delimiter = ";") where T : new()
{
    if (!File.Exists(fileFullPath))
    {
        return null;
    }

    var list = new List<T>();
    var csvFields = GetAllFieldOfClass<T>();
    var fieldDict = new Dictionary<int, MemberInfo>();

    using (TextFieldParser parser = new TextFieldParser(fileFullPath))
    {
        parser.SetDelimiters(delimiter);

        bool headerParsed = false;

        while (!parser.EndOfData)
        {
            //Processing row
            string[] rowFields = parser.ReadFields();
            if (!headerParsed)
            {
                for (int i = 0; i < rowFields.Length; i++)
                {
                    // First row shall be the header!
                    var csvField = csvFields.Where(f => f.Name == rowFields[i]).FirstOrDefault();
                    if (csvField != null)
                    {
                        fieldDict.Add(i, csvField);
                    }
                }
                headerParsed = true;
            }
            else
            {
                T newObj = new T();
                for (int i = 0; i < rowFields.Length; i++)
                {
                    var csvFied = fieldDict[i];
                    var record = rowFields[i];

                    if (csvFied is FieldInfo)
                    {
                        ((FieldInfo)csvFied).SetValue(newObj, record);
                    }
                    else if (csvFied is PropertyInfo)
                    {
                        var pi = (PropertyInfo)csvFied;
                        pi.SetValue(newObj, Convert.ChangeType(record, pi.PropertyType), null);
                    }
                    else
                    {
                        throw new Exception("Unhandled case.");
                    }
                }
                if (newObj != null)
                {
                    list.Add(newObj);
                }
            }
        }
    }
    return list;
}

public static IEnumerable<MemberInfo> GetAllFieldOfClass<T>()
{
    return
        from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        where new[] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
        let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
        orderby orderAttr == null ? int.MaxValue : orderAttr.Order, mi.Name
        select mi;            
}

0

Consiglio vivamente di usare CsvHelper.

Ecco un breve esempio:

public class csvExampleClass
{
    public string Id { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}

var items = DeserializeCsvFile<List<csvExampleClass>>( csvText );

public static List<T> DeserializeCsvFile<T>(string text)
{
    CsvReader csv = new CsvReader( new StringReader( text ) );
    csv.Configuration.Delimiter = ",";
    csv.Configuration.HeaderValidated = null;
    csv.Configuration.MissingFieldFound = null;
    return (List<T>)csv.GetRecords<T>();
}

La documentazione completa è disponibile all'indirizzo: https://joshclose.github.io/CsvHelper

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.