CSV-Datei in stark typisierte Datenstruktur in .Net importieren [geschlossen]

106

Wie kann eine CSV-Datei am besten in eine stark typisierte Datenstruktur importiert werden?

MattH
quelle
Schauen Sie sich FileHelpers Open Source Library an .
NotMyself
Dies ist ein Duplikat von stackoverflow.com/questions/1103495/…
Mark Meuer
7
In Anbetracht dessen, dass dies ein Jahr vor 1103495 erstellt wurde, denke ich, dass diese Frage ein Duplikat dieser Frage ist.
MattH
2
Danke, Matt. Ich habe nur versucht, sie miteinander zu verknüpfen, nicht anzugeben, welche zuerst kam. Sie werden sehen, dass ich genau den gleichen Text auf der anderen Frage habe, die auf diese Frage verweist. Gibt es eine bessere Möglichkeit, zwei Fragen miteinander zu verknüpfen?
Mark Meuer

Antworten:

74

Microsoft TextFieldParser von ist stabil und folgt RFC 4180 für CSV-Dateien. Lassen Sie sich nicht vom Microsoft.VisualBasicNamespace abschrecken. Es ist eine Standardkomponente in .NET Framework. Fügen Sie einfach einen Verweis auf die globale Microsoft.VisualBasicAssembly hinzu.

Wenn Sie für Windows kompilieren (im Gegensatz zu Mono) und nicht damit rechnen, "kaputte" (nicht RFC-kompatible) CSV-Dateien analysieren zu müssen, ist dies die naheliegende Wahl, da es kostenlos, uneingeschränkt und stabil ist. und aktiv unterstützt, von denen die meisten für FileHelpers nicht gesagt werden können.

Siehe auch: Gewusst wie: Lesen von durch Kommas getrennten Textdateien in Visual Basic für ein VB-Codebeispiel.

MarkJ
quelle
2
Es gibt eigentlich nichts VB-spezifisches an dieser Klasse außer dem unglücklicherweise benannten Namespace. Ich würde diese Bibliothek definitiv wählen, wenn ich nur einen "einfachen" CSV-Parser benötige, da es nichts gibt, worüber ich mich im Allgemeinen herunterladen, verteilen oder Sorgen machen könnte. Zu diesem Zweck habe ich die VB-fokussierte Formulierung aus dieser Antwort heraus bearbeitet.
Aaronaught
@Aaronaught Ich denke, deine Änderungen sind meistens eine Verbesserung. Obwohl dieser RFC nicht unbedingt maßgeblich ist, halten sich viele CSV-Autoren nicht daran, z. B. verwendet Excel in "CSV" -Dateien nicht immer ein Komma . Hat meine vorherige Antwort nicht bereits gesagt, dass die Klasse von C # aus verwendet werden kann?
MarkJ
Das TextFieldParserfunktioniert auch für tabulatorgetrennte und andere seltsame Excel-generierte Cruft. Ich weiß , dass Ihre vorherige Antwort nicht behauptet , dass die Bibliothek war VB-spezifisch, es kam nur zu mir herüber als was bedeutet , dass es wirklich war bedeutet für VB, und nicht beabsichtigt , von C # verwendet werden, was ich ist nicht denken der Fall - es gibt einige wirklich nützliche Klassen in MSVB.
Aaronaught
21

Verwenden Sie eine OleDB-Verbindung.

String sConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\InputDirectory\\;Extended Properties='text;HDR=Yes;FMT=Delimited'";
OleDbConnection objConn = new OleDbConnection(sConnectionString);
objConn.Open();
DataTable dt = new DataTable();
OleDbCommand objCmdSelect = new OleDbCommand("SELECT * FROM file.csv", objConn);
OleDbDataAdapter objAdapter1 = new OleDbDataAdapter();
objAdapter1.SelectCommand = objCmdSelect;
objAdapter1.Fill(dt);
objConn.Close();
Kevin
quelle
Dies erfordert Dateisystemzugriff. Soweit ich weiß, gibt es keine Möglichkeit, OLEDB mit In-Memory-Streams zum Laufen zu bringen :(
UserControl
3
@UserControl, natürlich erfordert es Dateisystemzugriff. Er fragte nach dem Import einer CSV-Datei
Kevin
1
Ich beschwere mich nicht. Tatsächlich würde ich die OLEDB-Lösung dem Rest vorziehen, aber ich war so oft frustriert, als ich CSV in ASP.NET-Anwendungen analysieren musste, und wollte es daher beachten.
UserControl
12

Wenn Sie ziemlich komplexe Szenarien für das CSV-Parsing erwarten, denken Sie nicht einmal daran, unseren eigenen Parser zu rollen . Es gibt viele hervorragende Tools wie FileHelpers oder sogar solche von CodeProject .

Der Punkt ist, dass dies ein ziemlich häufiges Problem ist und man könnte darauf wetten viele Softwareentwickler bereits über dieses Problem nachgedacht und es gelöst haben.

Jon Limjap
quelle
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert. - Von Review
Techspider
Vielen Dank an @techspider. Ich hoffe, Sie haben bemerkt, dass dieser Beitrag aus der Beta-Phase von StackOverflow stammt: D Allerdings werden CSV-Tools heutzutage besser aus Nuget-Paketen bezogen - daher bin ich mir nicht sicher, ob selbst Linkantworten gegen 8 Jahre immun sind -alte Evolutionszyklen der Technologie
Jon Limjap
9

Brian bietet eine gute Lösung für die Konvertierung in eine stark typisierte Sammlung.

Die meisten der angegebenen CSV-Analysemethoden berücksichtigen keine Escape-Felder oder einige andere Feinheiten von CSV-Dateien (wie das Trimmen von Feldern). Hier ist der Code, den ich persönlich benutze. Es ist ein bisschen rau an den Rändern und hat so gut wie keine Fehlerberichterstattung.

public static IList<IList<string>> Parse(string content)
{
    IList<IList<string>> records = new List<IList<string>>();

    StringReader stringReader = new StringReader(content);

    bool inQoutedString = false;
    IList<string> record = new List<string>();
    StringBuilder fieldBuilder = new StringBuilder();
    while (stringReader.Peek() != -1)
    {
        char readChar = (char)stringReader.Read();

        if (readChar == '\n' || (readChar == '\r' && stringReader.Peek() == '\n'))
        {
            // If it's a \r\n combo consume the \n part and throw it away.
            if (readChar == '\r')
            {
                stringReader.Read();
            }

            if (inQoutedString)
            {
                if (readChar == '\r')
                {
                    fieldBuilder.Append('\r');
                }
                fieldBuilder.Append('\n');
            }
            else
            {
                record.Add(fieldBuilder.ToString().TrimEnd());
                fieldBuilder = new StringBuilder();

                records.Add(record);
                record = new List<string>();

                inQoutedString = false;
            }
        }
        else if (fieldBuilder.Length == 0 && !inQoutedString)
        {
            if (char.IsWhiteSpace(readChar))
            {
                // Ignore leading whitespace
            }
            else if (readChar == '"')
            {
                inQoutedString = true;
            }
            else if (readChar == ',')
            {
                record.Add(fieldBuilder.ToString().TrimEnd());
                fieldBuilder = new StringBuilder();
            }
            else
            {
                fieldBuilder.Append(readChar);
            }
        }
        else if (readChar == ',')
        {
            if (inQoutedString)
            {
                fieldBuilder.Append(',');
            }
            else
            {
                record.Add(fieldBuilder.ToString().TrimEnd());
                fieldBuilder = new StringBuilder();
            }
        }
        else if (readChar == '"')
        {
            if (inQoutedString)
            {
                if (stringReader.Peek() == '"')
                {
                    stringReader.Read();
                    fieldBuilder.Append('"');
                }
                else
                {
                    inQoutedString = false;
                }
            }
            else
            {
                fieldBuilder.Append(readChar);
            }
        }
        else
        {
            fieldBuilder.Append(readChar);
        }
    }
    record.Add(fieldBuilder.ToString().TrimEnd());
    records.Add(record);

    return records;
}

Beachten Sie, dass dies nicht den Randfall von Feldern behandelt, die nicht durch doppelte Anführungszeichen getrennt werden, sondern von Meerley, der eine Zeichenfolge in Anführungszeichen enthält. In diesem Beitrag finden Sie eine bessere Erweiterung sowie einige Links zu geeigneten Bibliotheken.

ICR
quelle
9

Ich stimme @ NotMyself zu . FileHelpers ist gut getestet und behandelt alle Arten von , mit denen Sie sich möglicherweise befassen müssen, wenn Sie es selbst tun. Schauen Sie sich an, was FileHelpers macht, und schreiben Sie nur dann Ihre eigenen, wenn Sie absolut sicher sind, dass Sie entweder (1) niemals die Randfälle von FileHelpers behandeln müssen oder (2) diese Art von Dingen gerne schreiben und dies tun werden Seien Sie überglücklich, wenn Sie solche Dinge analysieren müssen:

1, "Bill", "Smith", "Supervisor", "No Comment"

2, 'Drake', 'O'Malley', "Hausmeister,

Ups, ich werde nicht zitiert und bin in einer neuen Zeile!

Jon Galloway
quelle
6

Ich war gelangweilt, also habe ich einige Sachen modifiziert, die ich geschrieben habe. Es wird versucht, das Parsing auf OO-Weise zu kapseln, während die Anzahl der Iterationen durch die Datei verringert wird. Es wird nur einmal oben in jedem Fall iteriert.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.IO;

namespace ConsoleApplication1
{
    class Program
    {

        static void Main(string[] args)
        {

            // usage:

            // note this wont run as getting streams is not Implemented

            // but will get you started

            CSVFileParser fileParser = new CSVFileParser();

            // TO Do:  configure fileparser

            PersonParser personParser = new PersonParser(fileParser);

            List<Person> persons = new List<Person>();
            // if the file is large and there is a good way to limit
            // without having to reparse the whole file you can use a 
            // linq query if you desire
            foreach (Person person in personParser.GetPersons())
            {
                persons.Add(person);
            }

            // now we have a list of Person objects
        }
    }

    public abstract  class CSVParser 
    {

        protected String[] deliniators = { "," };

        protected internal IEnumerable<String[]> GetRecords()
        {

            Stream stream = GetStream();
            StreamReader reader = new StreamReader(stream);

            String[] aRecord;
            while (!reader.EndOfStream)
            {
                  aRecord = reader.ReadLine().Split(deliniators,
                   StringSplitOptions.None);

                yield return aRecord;
            }

        }

        protected abstract Stream GetStream(); 

    }

    public class CSVFileParser : CSVParser
    {
        // to do: add logic to get a stream from a file

        protected override Stream GetStream()
        {
            throw new NotImplementedException();
        } 
    }

    public class CSVWebParser : CSVParser
    {
        // to do: add logic to get a stream from a web request

        protected override Stream GetStream()
        {
            throw new NotImplementedException();
        }
    }

    public class Person
    {
        public String Name { get; set; }
        public String Address { get; set; }
        public DateTime DOB { get; set; }
    }

    public class PersonParser 
    {

        public PersonParser(CSVParser parser)
        {
            this.Parser = parser;
        }

        public CSVParser Parser { get; set; }

        public  IEnumerable<Person> GetPersons()
        {
            foreach (String[] record in this.Parser.GetRecords())
            {
                yield return new Person()
                {
                    Name = record[0],
                    Address = record[1],
                    DOB = DateTime.Parse(record[2]),
                };
            }
        }
    }
}
Brian Leahy
quelle
2

Eine gute einfache Möglichkeit besteht darin, die Datei zu öffnen und jede Zeile in ein Array, eine verknüpfte Liste und eine Datenstruktur Ihrer Wahl einzulesen. Seien Sie jedoch vorsichtig beim Umgang mit der ersten Zeile.

Dies mag über Ihrem Kopf liegen, aber es scheint eine direkte Möglichkeit zu geben, auch über eine Verbindungszeichenfolge darauf zuzugreifen .

Warum nicht versuchen, Python anstelle von C # oder VB zu verwenden? Es hat ein schönes CSV-Modul zum Importieren, das das ganze schwere Heben für Sie erledigt.

halloandre
quelle
1
Springen Sie nicht von VB zu Python, um einen CSV-Parser zu erhalten. Es gibt einen in VB. Obwohl seltsamerweise scheint es in den Antworten auf diese Frage ignoriert worden zu sein. msdn.microsoft.com/en-us/library/…
MarkJ
1

Ich musste diesen Sommer einen CSV-Parser in .NET für ein Projekt verwenden und entschied mich für den Microsoft Jet Text Driver. Sie geben einen Ordner mithilfe einer Verbindungszeichenfolge an und fragen dann eine Datei mithilfe einer SQL Select-Anweisung ab. Sie können starke Typen mithilfe einer schema.ini-Datei angeben. Ich habe dies zuerst nicht getan, aber dann bekam ich schlechte Ergebnisse, bei denen der Datentyp nicht sofort ersichtlich war, wie z. B. IP-Nummern oder ein Eintrag wie "XYQ 3.9 SP1".

Eine Einschränkung, auf die ich gestoßen bin, ist, dass es keine Spaltennamen mit mehr als 64 Zeichen verarbeiten kann. es schneidet ab. Dies sollte kein Problem sein, außer ich hatte es mit sehr schlecht gestalteten Eingabedaten zu tun. Es gibt ein ADO.NET DataSet zurück.

Dies war die beste Lösung, die ich gefunden habe. Ich wäre vorsichtig, wenn ich meinen eigenen CSV-Parser rollen würde, da ich wahrscheinlich einige der Endfälle verpassen würde und ich keine anderen kostenlosen CSV-Parsing-Pakete für .NET da draußen gefunden habe.

BEARBEITEN: Außerdem kann es nur eine schema.ini-Datei pro Verzeichnis geben, daher habe ich sie dynamisch angehängt, um die erforderlichen Spalten stark einzugeben. Es werden nur die angegebenen Spalten stark eingegeben und auf nicht angegebene Felder geschlossen. Ich habe dies sehr geschätzt, da ich mich mit dem Importieren einer flüssigen CSV-Spalte mit mehr als 70 Spalten befasste und nicht jede Spalte angeben wollte, sondern nur die fehlerhaften.

pbh101
quelle
Warum nicht der in VB.NET integrierte CSV-Parser? msdn.microsoft.com/en-us/library/…
MarkJ
1

Ich habe einen Code eingegeben. Das Ergebnis im Datagridviewer sah gut aus. Es analysiert eine einzelne Textzeile in einer Arrayliste von Objekten.

    enum quotestatus
    {
        none,
        firstquote,
        secondquote
    }
    public static System.Collections.ArrayList Parse(string line,string delimiter)
    {        
        System.Collections.ArrayList ar = new System.Collections.ArrayList();
        StringBuilder field = new StringBuilder();
        quotestatus status = quotestatus.none;
        foreach (char ch in line.ToCharArray())
        {                                
            string chOmsch = "char";
            if (ch == Convert.ToChar(delimiter))
            {
                if (status== quotestatus.firstquote)
                {
                    chOmsch = "char";
                }                         
                else
                {
                    chOmsch = "delimiter";                    
                }                    
            }

            if (ch == Convert.ToChar(34))
            {
                chOmsch = "quotes";           
                if (status == quotestatus.firstquote)
                {
                    status = quotestatus.secondquote;
                }
                if (status == quotestatus.none )
                {
                    status = quotestatus.firstquote;
                }
            }

            switch (chOmsch)
            {
                case "char":
                    field.Append(ch);
                    break;
                case "delimiter":                        
                    ar.Add(field.ToString());
                    field.Clear();
                    break;
                case "quotes":
                    if (status==quotestatus.firstquote)
                    {
                        field.Clear();                            
                    }
                    if (status== quotestatus.secondquote)
                    {                                                                           
                            status =quotestatus.none;                                
                    }                    
                    break;
            }
        }
        if (field.Length != 0)            
        {
            ar.Add(field.ToString());                
        }           
        return ar;
    }
Pieter
quelle
0

Wenn Sie garantieren können, dass die Daten keine Kommas enthalten, ist es wahrscheinlich am einfachsten, String.split zu verwenden .

Beispielsweise:

String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);

Möglicherweise gibt es Bibliotheken, mit denen Sie helfen können, aber das ist wahrscheinlich so einfach wie möglich. Stellen Sie nur sicher, dass die Daten keine Kommas enthalten können, da Sie sie sonst besser analysieren müssen.

Mike Stone
quelle
Dies ist keine optimale Lösung
Roundcrisis
Sehr schlecht für die Speichernutzung und viel Overhead. Klein sollte dank ein paar Kilobyte weniger sein. Auf jeden Fall nicht gut für eine 10mb CSV!
Piotr Kula
Dies hängt von der Größe Ihres Speichers und der Datei ab.
Tonymiao