Lesen von CSV-Dateien mit C #

169

Ich schreibe eine einfache Importanwendung und muss eine CSV-Datei lesen, das Ergebnis in a DataGridanzeigen und beschädigte Zeilen der CSV-Datei in einem anderen Raster anzeigen. Zeigen Sie beispielsweise die Linien an, die kürzer als 5 Werte in einem anderen Raster sind. Ich versuche das so zu machen:

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

In diesem Fall ist es jedoch sehr schwierig, Arrays zu bearbeiten. Gibt es eine bessere Möglichkeit, die Werte aufzuteilen?

ilkin
quelle
Vielen Dank für Ihre Lösung. Erwägen Sie, es als Antwortbeitrag zu veröffentlichen - die Aufnahme in die Frage trägt nicht zur Lesbarkeit bei.
BartoszKP

Antworten:

363

Das Rad nicht neu erfinden. Nutzen Sie die Vorteile von .NET BCL.

  • Fügen Sie einen Verweis auf das hinzu Microsoft.VisualBasic(ja, es steht VisualBasic, aber es funktioniert genauso gut in C # - denken Sie daran, dass am Ende alles nur IL ist)
  • Verwenden Sie die Microsoft.VisualBasic.FileIO.TextFieldParserKlasse, um die CSV-Datei zu analysieren

Hier ist der Beispielcode:

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

Es funktioniert gut für mich in meinen C # -Projekten.

Hier sind einige weitere Links / Informationen:

David Pokluda
quelle
18
Ich wünschte wirklich, es gäbe einen Weg, der keine VB-Bibliotheken verwendet, aber das hat perfekt funktioniert! Danke dir!
Gillonba
5
+1: Ich habe gerade den Lumenworks Fast CSV-Reader für eine 53-MB-Datei kaputt gemacht. Es sieht so aus, als ob das Zeilen-Caching nach 43.000 Zeilen fehlgeschlagen ist und den Puffer verschlüsselt hat. Versuchte die VB TextFieldParserund es hat den Trick getan. Danke
Gone Coding
10
+1 Tolle Antwort, da ich finde, dass viele Leute nicht wissen, dass diese Klasse existiert. Für zukünftige Betrachter ist zu beachten, dass beim parser.TextFieldType = FieldType.Delimited;Aufrufen keine Einstellung erforderlich ist parser.SetDelimiters(",");, da die Methode die TextFieldTypeEigenschaft für Sie festlegt .
Brian
10
Überprüfen Sie dies auch: dotnetperls.com/textfieldparser . TextFieldParser hat eine schlechtere Leistung als String.Split und StreamReader. Es gibt jedoch einen großen Unterschied zwischen string.Split und TextFieldParser. TextFieldParser behandelt seltsame Fälle wie ein Komma in einer Spalte: Sie können eine Spalte wie benennen "text with quote"", and comma"und den richtigen Wert text with quote", and commaanstelle von falsch getrennten Werten erhalten. Sie können sich also für String.Split entscheiden, wenn Sie csv sehr einfach ist.
Yongwei Wu
5
Beachten Sie, dass Sie möglicherweise einen Verweis auf Microsoft.VisualBasic hinzufügen müssen, um dies zu verwenden. Klicken Sie in Visual Studio mit der rechten Maustaste auf Ihr Projekt, wählen Sie "Hinzufügen"> "Referenz" und aktivieren Sie das Kontrollkästchen für Microsoft.VisualBasic.
Derek Kurth
37

Ich habe die Erfahrung gemacht, dass es viele verschiedene CSV-Formate gibt. Insbesondere, wie sie mit dem Escapezeichen von Anführungszeichen und Trennzeichen innerhalb eines Felds umgehen.

Dies sind die Varianten, auf die ich gestoßen bin:

  • Anführungszeichen werden in Anführungszeichen gesetzt und verdoppelt (Excel), dh 15 "-> Feld1," 15 "", Feld3
  • Anführungszeichen werden nur geändert, wenn das Feld aus einem anderen Grund in Anführungszeichen gesetzt wird. dh 15 "-> Feld1,15", Felder3
  • Anführungszeichen werden mit \ maskiert. dh 15 "-> Feld1," 15 "", Feld3
  • Anführungszeichen werden überhaupt nicht geändert (dies ist nicht immer richtig zu analysieren)
  • Trennzeichen wird zitiert (Excel). dh a, b -> Feld1, "a, b", Feld3
  • Trennzeichen wird mit \ maskiert. dh a, b -> Feld1, a \, b, Feld3

Ich habe viele der vorhandenen CSV-Parser ausprobiert, aber es gibt keinen einzigen, der die Varianten verarbeiten kann, auf die ich gestoßen bin. Es ist auch schwierig, aus der Dokumentation herauszufinden, welche Escape-Varianten die Parser unterstützen.

In meinen Projekten verwende ich jetzt entweder den VB TextFieldParser oder einen benutzerdefinierten Splitter.

adrianm
quelle
1
Ich liebe diese Antwort für die Testfälle, die Sie bereitgestellt haben!
Matthew Rodatus
2
Das Hauptproblem besteht darin, dass sich die meisten Implementierungen nicht für RFC 4180 interessieren, das das CSV-Format beschreibt und wie Trennzeichen maskiert werden sollten.
Jenny O'Reilly
RFC-4180 stammt aus dem Jahr 2005, was jetzt alt zu sein scheint, aber denken Sie daran: Das .Net-Framework wurde erstmals 2001 veröffentlicht. Außerdem sind RFCs nicht immer offizielle Standards und haben in diesem Fall nicht das gleiche Gewicht wie beispielsweise , ISO-8601 oder RFC-761.
Joel Coehoorn
23

Ich empfehle CsvHelper von Nuget .

(Das Hinzufügen eines Verweises auf Microsoft.VisualBasic fühlt sich einfach nicht richtig an, es ist nicht nur hässlich, es ist wahrscheinlich nicht einmal plattformübergreifend.)

knocte
quelle
2
Es ist genau so plattformübergreifend wie C #.
PRMan
Falsch, Microsoft.VisualBasic.dll unter Linux stammt aus Mono-Quellen, die eine andere Implementierung als Microsoft haben, und es gibt einige Dinge, die nicht implementiert sind, zum Beispiel: stackoverflow.com/questions/6644165/…
knocte
(Außerdem hatte die VB-Sprache unter den Unternehmen, die an der Erstellung / Entwicklung des Mono-Projekts beteiligt waren, noch nie einen Schwerpunkt, so dass sie im Vergleich zum C #
-Ökosystem
1
Nachdem ich mit beiden gespielt habe, möchte ich hinzufügen, dass CsvHelperder Klassen-Mapper eine integrierte Zeile enthält. Es ermöglicht Variationen in den Spaltenüberschriften (falls vorhanden) und sogar anscheinend Variationen in der Spaltenreihenfolge (obwohl ich letztere selbst nicht getestet habe). Alles in allem fühlt es sich viel "High-Level" an als TextFieldParser.
David
1
Ja, der Microsoft.VisualBasic-Namespace ist unter .NET Core 2.1
N4ppeL
13

Manchmal ist die Verwendung von Bibliotheken cool, wenn Sie das Rad nicht neu erfinden möchten. In diesem Fall kann man jedoch denselben Job mit weniger Codezeilen ausführen und ist im Vergleich zur Verwendung von Bibliotheken einfacher zu lesen. Hier ist ein anderer Ansatz, den ich sehr einfach finde.

  1. In diesem Beispiel verwende ich StreamReader, um die Datei zu lesen
  2. Regex, um das Trennzeichen aus jeder Zeile (n) zu erkennen.
  3. Ein Array zum Sammeln der Spalten von Index 0 bis 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 */
        }
    }
Mana
quelle
4
Sicher hat das Probleme mit Daten, die selbst neue Zeilen enthalten?
Doogal
Jetzt wissen CSV-Datendateien nicht, dass sie leere Zeilen zwischen Daten enthalten. Wenn Sie jedoch eine Quelle haben, die dies tut, würde ich in diesem Fall einfach einen einfachen Regex-Test durchführen, um Leerzeichen oder Zeilen zu entfernen, die nichts enthalten, bevor Sie den Reader ausführen. Überprüfen Sie hier für verschiedene Beispiele: stackoverflow.com/questions/7647716/…
Mana
1
Sicherlich ist ein char-basierter Ansatz für diese Art von Problem natürlicher als ein Regex. Abhängig vom Vorhandensein von Anführungszeichen soll das Verhalten unterschiedlich sein.
Casey
6

CSV kann kompliziert werden echte schnell.

Verwenden Sie etwas Robustes und Getestetes:
FileHelpers: www.filehelpers.net

Die FileHelpers sind eine kostenlose und einfach zu verwendende .NET-Bibliothek zum Importieren / Exportieren von Daten aus festen oder begrenzten Datensätzen in Dateien, Zeichenfolgen oder Streams.

Keith bläst
quelle
5
Ich denke, FileHelper versucht zu viel auf einmal zu tun. Das Parsen von Dateien ist ein zweistufiger Prozess, bei dem Sie zuerst Zeilen in Felder aufteilen und dann die Felder in Daten analysieren. Die Kombination der Funktionen macht es schwierig, Dinge wie Master-Detail und Linienfilterung zu handhaben.
Adrianm
4

Ein weiterer auf dieser Liste, Cinchoo ETL - eine Open-Source-Bibliothek zum Lesen und Schreiben von CSV-Dateien

Eine Beispiel-CSV-Datei finden Sie unten

Id, Name
1, Tom
2, Mark

Schnell können Sie sie mithilfe der Bibliothek wie folgt laden

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

Wenn Sie eine POCO-Klasse haben, die mit der CSV-Datei übereinstimmt

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

Sie können es verwenden, um die CSV-Datei wie folgt zu laden

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

Weitere Informationen zur Verwendung finden Sie in den Artikeln bei CodeProject .

Haftungsausschluss: Ich bin der Autor dieser Bibliothek

RajN
quelle
Hallo, können Sie CSV in SQL-Tabelle laden - ich kenne den Header in der CSV-Tabelle vorher nicht. Spiegeln Sie einfach, was in CSV zu SQL-Tabelle ist
Aggie
Ja, du kannst. Bitte sehen Sie diesen 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();
                    }
                }

         }
anongf4gsdfg54564533
quelle
Woher haben Sie diese Lösung kopiert?
MindRoasterMir
0

Zunächst müssen Sie verstehen, was CSV ist und wie man es schreibt.

  1. Jede nächste Zeichenfolge ( /r/n) ist die nächste "Tabellen" -Zeile.
  2. "Tabellen" -Zellen sind durch ein Trennzeichen getrennt. Am häufigsten verwendete Symbole sind \toder,
  3. Jede Zelle kann möglicherweise dieses Trennzeichen enthalten (die Zelle muss mit dem Anführungszeichen beginnen und endet in diesem Fall mit diesem Symbol).
  4. Jede Zelle kann möglicherweise /r/nSybols enthalten (Zelle muss mit dem Anführungszeichen beginnen und endet in diesem Fall mit diesem Symbol)

Der einfachste Weg für C # / Visual Basic, mit CSV-Dateien zu arbeiten, ist die Verwendung einer Standardbibliothek Microsoft.VisualBasic. Sie müssen Ihrer Klasse nur die erforderliche Referenz und die folgende Zeichenfolge hinzufügen:

using Microsoft.VisualBasic.FileIO;

Ja, Sie können es in C # verwenden, keine Sorge. Diese Bibliothek kann relativ große Dateien lesen und unterstützt alle erforderlichen Regeln, sodass Sie mit allen CSV-Dateien arbeiten können.

Vor einiger Zeit hatte ich eine einfache Klasse für CSV-Lesen / Schreiben basierend auf dieser Bibliothek geschrieben. Mit dieser einfachen Klasse können Sie mit CSV wie mit einem zweidimensionalen Array arbeiten. Sie finden meine Klasse unter folgendem Link: https://github.com/ukushu/DataExporter

Einfaches Beispiel für die Verwendung von:

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");
Andrew
quelle
0

Um die vorherigen Antworten zu vervollständigen, benötigt man möglicherweise eine Sammlung von Objekten aus seiner CSV-Datei, die entweder vom TextFieldParseroder vom analysiert werdenstring.Split Methode und dann jede Zeile über Reflection in ein Objekt konvertiert wird. Sie müssen natürlich zuerst eine Klasse definieren, die den Zeilen der CSV-Datei entspricht.

Ich habe den einfachen CSV-Serializer von Michael Kropat verwendet, der hier zu finden ist: Generische Klasse für CSV (alle Eigenschaften) und seine Methoden wiederverwendet, um die Felder und Eigenschaften der gewünschten Klasse abzurufen.

Ich deserialisiere meine CSV-Datei mit der folgenden Methode:

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;            
}
EricBDev
quelle
0

Ich würde dringend empfehlen, CsvHelper zu verwenden.

Hier ist ein kurzes Beispiel:

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

Die vollständige Dokumentation finden Sie unter: https://joshclose.github.io/CsvHelper

Kieran
quelle