Wie kann eine CSV-Datei am besten in eine stark typisierte Datenstruktur importiert werden?
Wie kann eine CSV-Datei am besten in eine stark typisierte Datenstruktur importiert werden?
Antworten:
Microsoft TextFieldParser von ist stabil und folgt RFC 4180 für CSV-Dateien. Lassen Sie sich nicht vom Microsoft.VisualBasic
Namespace abschrecken. Es ist eine Standardkomponente in .NET Framework. Fügen Sie einfach einen Verweis auf die globale Microsoft.VisualBasic
Assembly 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.
TextFieldParser
funktioniert 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.
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();
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.
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.
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!
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]),
};
}
}
}
}
Es gibt zwei Artikel zu CodeProject, die Code für eine Lösung bereitstellen, einen, der StreamReader verwendet, und einen, der CSV-Daten mithilfe des Microsoft- Texttreibers importiert .
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.
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.
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;
}
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.