c # datierbar auf csv

112

Könnte mir bitte jemand sagen, warum der folgende Code nicht funktioniert. Die Daten werden in der CSV-Datei gespeichert, die Daten werden jedoch nicht getrennt. Es existiert alles in der ersten Zelle jeder Zeile.

StringBuilder sb = new StringBuilder();

foreach (DataColumn col in dt.Columns)
{
    sb.Append(col.ColumnName + ',');
}

sb.Remove(sb.Length - 1, 1);
sb.Append(Environment.NewLine);

foreach (DataRow row in dt.Rows)
{
    for (int i = 0; i < dt.Columns.Count; i++)
    {
        sb.Append(row[i].ToString() + ",");
    }

    sb.Append(Environment.NewLine);
}

File.WriteAllText("test.csv", sb.ToString());

Vielen Dank.

Darren Young
quelle
Sie können dies überprüfen gist.github.com/riyadparvez/4467668
Benutzer
Ich habe eine Hochleistungserweiterung entwickelt. Überprüfen Sie diese Antwort
Nigje

Antworten:

229

Die folgende kürzere Version wird in Excel problemlos geöffnet. Möglicherweise war Ihr Problem das nachfolgende Komma

.net = 3.5

StringBuilder sb = new StringBuilder(); 

string[] columnNames = dt.Columns.Cast<DataColumn>().
                                  Select(column => column.ColumnName).
                                  ToArray();
sb.AppendLine(string.Join(",", columnNames));

foreach (DataRow row in dt.Rows)
{
    string[] fields = row.ItemArray.Select(field => field.ToString()).
                                    ToArray();
    sb.AppendLine(string.Join(",", fields));
}

File.WriteAllText("test.csv", sb.ToString());

.net> = 4.0

Und wie Tim betonte, können Sie es noch kürzer machen, wenn Sie auf .net> = 4 sind:

StringBuilder sb = new StringBuilder(); 

IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                  Select(column => column.ColumnName);
sb.AppendLine(string.Join(",", columnNames));

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

File.WriteAllText("test.csv", sb.ToString());

Ersetzen Sie, wie von Christian vorgeschlagen, den Schleifenblock durch:

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

Und letzter Vorschlag: Sie könnten den CSV-Inhalt Zeile für Zeile anstatt als ganzes Dokument schreiben, um zu vermeiden, dass sich ein großes Dokument im Speicher befindet.

vc 74
quelle
2
Sie müssen das nicht ItemArrayin ein neues kopieren String[], sondern können es .ToArray()mit .NET 4 weglassen und die String.JoinÜberlastung verwenden , die ein IEnumerable<T>(bearbeitetes) erfordert .
Tim Schmelter
2
@ TimSchmelter, ja, aber diese Überladungen wurden in .net4 eingeführt. Der Code wird nicht kompiliert, wenn das OP .net <4
vc 74
18
Diese Methode berücksichtigt kein Komma innerhalb eines Spaltenwerts.
Christian
2
Stattdessen IEnumerable <string> fields = row.ItemArray.Select (field => field.ToString (). Replace ("", "")); sb.AppendLine ("" + string.Join ("", "", Felder) + "");
Christian
2
@ Si8 Was meinst du? Diese Antwort verwendet nur Datenbankkomponenten und &nbspist typisch für HTML / XML-Dokumente. Es ist nicht der obige Code, der es erzeugt, es sei denn, die Tabelle enthält &nbsp;explizit
vc 74
36

Ich habe dies in eine Erweiterungsklasse eingepackt, mit der Sie Folgendes aufrufen können:

myDataTable.WriteToCsvFile("C:\\MyDataTable.csv");

auf einer beliebigen Datentabelle.

public static class DataTableExtensions 
{
    public static void WriteToCsvFile(this DataTable dataTable, string filePath) 
    {
        StringBuilder fileContent = new StringBuilder();

        foreach (var col in dataTable.Columns) 
        {
            fileContent.Append(col.ToString() + ",");
        }

        fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);

        foreach (DataRow dr in dataTable.Rows) 
        {
            foreach (var column in dr.ItemArray) 
            {
                fileContent.Append("\"" + column.ToString() + "\",");
            }

            fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);
        }

        System.IO.File.WriteAllText(filePath, fileContent.ToString());
    }
}
Paul Grimshaw
quelle
24

Eine neue Erweiterungsfunktion basierend auf Paul Grimshaws Antwort. Ich habe es bereinigt und die Möglichkeit hinzugefügt, mit unerwarteten Daten umzugehen. (Leere Daten, eingebettete Anführungszeichen und Kommas in den Überschriften ...)

Es wird auch eine Zeichenfolge zurückgegeben, die flexibler ist. Es gibt Null zurück, wenn das Tabellenobjekt keine Struktur enthält.

    public static string ToCsv(this DataTable dataTable) {
        StringBuilder sbData = new StringBuilder();

        // Only return Null if there is no structure.
        if (dataTable.Columns.Count == 0)
            return null;

        foreach (var col in dataTable.Columns) {
            if (col == null)
                sbData.Append(",");
            else
                sbData.Append("\"" + col.ToString().Replace("\"", "\"\"") + "\",");
        }

        sbData.Replace(",", System.Environment.NewLine, sbData.Length - 1, 1);

        foreach (DataRow dr in dataTable.Rows) {
            foreach (var column in dr.ItemArray) {
                if (column == null)
                    sbData.Append(",");
                else
                    sbData.Append("\"" + column.ToString().Replace("\"", "\"\"") + "\",");
            }
            sbData.Replace(",", System.Environment.NewLine, sbData.Length - 1, 1);
        }

        return sbData.ToString();
    }

Sie nennen es wie folgt:

var csvData = dataTableOject.ToCsv();
AnthonyVO
quelle
1
Dieser ist der beste aus dem Rest hier. Gut gemacht. Vielen Dank
Fandango68
Fantastische Lösung. Lokale Kommentare hinzugefügt, konnte aber sofort verwendet werden, ohne den Berg besteigen zu müssen. Danke dir.
j.hull
Liebte das! Ich habe es als nicht statische Methode verwendet und gerade meine DataTable als Parameter übergeben. Hat super funktioniert, danke.
Kid Koder
9

Wenn Ihr aufrufender Code auf die System.Windows.FormsAssembly verweist , können Sie einen radikal anderen Ansatz in Betracht ziehen. Meine Strategie besteht darin, die bereits vom Framework bereitgestellten Funktionen zu verwenden, um dies in sehr wenigen Codezeilen zu erreichen, ohne Spalten und Zeilen durchlaufen zu müssen. Der folgende Code erstellt programmgesteuert ein DataGridViewOn-the-Fly und setzt das DataGridView.DataSourceauf DataTable. Als nächstes wähle ich programmgesteuert alle Zellen (einschließlich der Kopfzeile) im DataGridViewAufruf aus DataGridView.GetClipboardContent()und platziere die Ergebnisse in Windows Clipboard. Dann füge ich den Inhalt der Zwischenablage in einen Aufruf von ein File.WriteAllText()und stelle dabei sicher, dass die Formatierung des Einfügens als angegeben wird TextDataFormat.CommaSeparatedValue.

Hier ist der Code:

public static void DataTableToCSV(DataTable Table, string Filename)
{
    using(DataGridView dataGrid = new DataGridView())
    {
        // Save the current state of the clipboard so we can restore it after we are done
        IDataObject objectSave = Clipboard.GetDataObject();

        // Set the DataSource
        dataGrid.DataSource = Table;
        // Choose whether to write header. Use EnableWithoutHeaderText instead to omit header.
        dataGrid.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;
        // Select all the cells
        dataGrid.SelectAll();
        // Copy (set clipboard)
        Clipboard.SetDataObject(dataGrid.GetClipboardContent());
        // Paste (get the clipboard and serialize it to a file)
        File.WriteAllText(Filename,Clipboard.GetText(TextDataFormat.CommaSeparatedValue));              

        // Restore the current state of the clipboard so the effect is seamless
        if(objectSave != null) // If we try to set the Clipboard to an object that is null, it will throw...
        {
            Clipboard.SetDataObject(objectSave);
        }
    }
}

Beachten Sie, dass ich auch sicherstellen muss, dass der Inhalt der Zwischenablage erhalten bleibt, bevor ich beginne, und ihn wiederherstelle, sobald ich fertig bin, damit der Benutzer beim nächsten Versuch, ihn einzufügen, keinen unerwarteten Müll bekommt. Die wichtigsten Einschränkungen bei diesem Ansatz sind: 1) Ihre Klasse muss sich darauf beziehenSystem.Windows.Forms , was in einer Datenabstraktionsschicht möglicherweise nicht der Fall ist. 2) Ihre Assembly muss auf das .NET 4.5-Framework ausgerichtet sein, da DataGridView in 4.0 nicht vorhanden ist. und 3) Die Methode schlägt fehl, wenn die Zwischenablage von einem anderen Prozess verwendet wird.

Auf jeden Fall ist dieser Ansatz möglicherweise nicht für Ihre Situation geeignet, aber dennoch interessant und kann ein weiteres Werkzeug in Ihrer Toolbox sein.

Adam White
quelle
1
Die Verwendung der Zwischenablage ist nicht erforderlich. stackoverflow.com/questions/40726017/… . .GetClipboardContentbehandelt auch einige Randfälle von Werten, die enthalten ,. ", \t(es wandelt Tab in Leerzeichen um)
Slai
2
Das ist gut, aber was ist, wenn jemand die Maschine gleichzeitig benutzt und im kritischen Moment etwas in die Zwischenablage legt?
Ayo Adesina
7

Ich habe dies kürzlich getan, aber meine Werte in doppelte Anführungszeichen gesetzt.

Ändern Sie beispielsweise diese beiden Zeilen:

sb.Append("\"" + col.ColumnName + "\","); 
...
sb.Append("\"" + row[i].ToString() + "\","); 
Ben Jakuben
quelle
Vielen Dank für den Vorschlag, aber befinden sich alle Daten noch in der ersten Zelle jeder Zeile?
Darren Young
7

Versuchen Sie , sb.Append(Environment.NewLine);zu sb.AppendLine();.

StringBuilder sb = new StringBuilder();          
foreach (DataColumn col in dt.Columns)         
{             
    sb.Append(col.ColumnName + ',');         
}          

sb.Remove(sb.Length - 1, 1);         
sb.AppendLine();          

foreach (DataRow row in dt.Rows)         
{             
    for (int i = 0; i < dt.Columns.Count; i++)             
    {                 
        sb.Append(row[i].ToString() + ",");             
    }              

    sb.AppendLine();         
}          

File.WriteAllText("test.csv", sb.ToString());
Neil Knight
quelle
Das ergibt dann zwei Carraige-Renditen.
Darren Young
@alexl: Das ist, was ich ursprünglich wollte, aber es war aus meinem Kopf, bis VS feuerte: o)
Neil Knight
5

Versuchen Sie , setzen ;statt,

Ich hoffe es hilft

alexl
quelle
5

Dies und das lesen ?


Eine bessere Umsetzung wäre

var result = new StringBuilder();
for (int i = 0; i < table.Columns.Count; i++)
{
    result.Append(table.Columns[i].ColumnName);
    result.Append(i == table.Columns.Count - 1 ? "\n" : ",");
}

foreach (DataRow row in table.Rows)
{
    for (int i = 0; i < table.Columns.Count; i++)
    {
        result.Append(row[i].ToString());
        result.Append(i == table.Columns.Count - 1 ? "\n" : ",");
    }
}
 File.WriteAllText("test.csv", result.ToString());
Naveen
quelle
5

Der Fehler ist das Listentrennzeichen.

Anstatt zu schreiben sb.Append(something... + ','), sollten Sie so etwas wie setzensb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);

Sie müssen das in Ihrem Betriebssystem konfigurierte Listentrennzeichen (wie im obigen Beispiel) oder das Listentrennzeichen auf dem Clientcomputer einfügen, auf dem die Datei überwacht werden soll. Eine andere Möglichkeit wäre, es in der app.config oder web.config als Parameter Ihrer Anwendung zu konfigurieren.

Martín Delafuente
quelle
5

4 Codezeilen:

public static string ToCSV(DataTable tbl)
{
    StringBuilder strb = new StringBuilder();

    //column headers
    strb.AppendLine(string.Join(",", tbl.Columns.Cast<DataColumn>()
        .Select(s => "\"" + s.ColumnName + "\"")));

    //rows
    tbl.AsEnumerable().Select(s => strb.AppendLine(
        string.Join(",", s.ItemArray.Select(
            i => "\"" + i.ToString() + "\"")))).ToList();

    return strb.ToString();
}

Beachten Sie, dass das ToList()am Ende wichtig ist; Ich brauche etwas, um eine Ausdrucksbewertung zu erzwingen. Wenn ich Code Golf spielen würde, könnte ich Min()stattdessen verwenden.

Beachten Sie auch, dass das Ergebnis aufgrund des letzten Aufrufs von am Ende eine neue Zeile enthält AppendLine(). Sie können dies nicht wollen. Sie können einfach anrufen TrimEnd(), um es zu entfernen.

user2023861
quelle
3

Hier ist eine Erweiterung des Beitrags von vc-74, die Kommas genauso behandelt wie Excel. Excel setzt Daten in Anführungszeichen, wenn die Daten ein Komma haben, aber keine Anführungszeichen, wenn die Daten kein Komma haben.

    public static string ToCsv(this DataTable inDataTable, bool inIncludeHeaders = true)
    {
        var builder = new StringBuilder();
        var columnNames = inDataTable.Columns.Cast<DataColumn>().Select(column => column.ColumnName);
        if (inIncludeHeaders)
            builder.AppendLine(string.Join(",", columnNames));
        foreach (DataRow row in inDataTable.Rows)
        {
            var fields = row.ItemArray.Select(field => field.ToString().WrapInQuotesIfContains(","));
            builder.AppendLine(string.Join(",", fields));
        }

        return builder.ToString();
    }

    public static string WrapInQuotesIfContains(this string inString, string inSearchString)
    {
        if (inString.Contains(inSearchString))
            return "\"" + inString+ "\"";
        return inString;
    }
Rhyous
quelle
2

Um in eine Datei zu schreiben, halte ich die folgende Methode für die effizienteste und einfachste: (Sie können Anführungszeichen hinzufügen, wenn Sie möchten)

public static void WriteCsv(DataTable dt, string path)
{
    using (var writer = new StreamWriter(path)) {
        writer.WriteLine(string.Join(",", dt.Columns.Cast<DataColumn>().Select(dc => dc.ColumnName)));
        foreach (DataRow row in dt.Rows) {
            writer.WriteLine(string.Join(",", row.ItemArray));
        }
    }
}
Student222
quelle
2

So ahmen Sie Excel CSV nach:

public static string Convert(DataTable dt)
{
    StringBuilder sb = new StringBuilder();

    IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                        Select(column => column.ColumnName);
    sb.AppendLine(string.Join(",", columnNames));

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

    return sb.ToString().Trim();
}
James Carter
quelle
1
StringBuilder sb = new StringBuilder();
        SaveFileDialog fileSave = new SaveFileDialog();
        IEnumerable<string> columnNames = tbCifSil.Columns.Cast<DataColumn>().
                                          Select(column => column.ColumnName);
        sb.AppendLine(string.Join(",", columnNames));

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

        fileSave.ShowDialog();
        File.WriteAllText(fileSave.FileName, sb.ToString());
Nam Nguyễn
quelle
Willkommen bei StackOverflow! Antworten sind am besten, wenn sie eine Beschreibung des Code-Snippets enthalten. Ich persönlich habe festgestellt, dass Variablennamen, die zwischen Frage und Antwort stehen, für mich hilfreicher sind.
AWinkle
1
public void ExpoetToCSV(DataTable dtDataTable, string strFilePath)
{

    StreamWriter sw = new StreamWriter(strFilePath, false);
    //headers   
    for (int i = 0; i < dtDataTable.Columns.Count; i++)
    {
        sw.Write(dtDataTable.Columns[i].ToString().Trim());
        if (i < dtDataTable.Columns.Count - 1)
        {
            sw.Write(",");
        }
    }
    sw.Write(sw.NewLine);
    foreach (DataRow dr in dtDataTable.Rows)
    {
        for (int i = 0; i < dtDataTable.Columns.Count; i++)
        {
            if (!Convert.IsDBNull(dr[i]))
            {
                string value = dr[i].ToString().Trim();
                if (value.Contains(','))
                {
                    value = String.Format("\"{0}\"", value);
                    sw.Write(value);
                }
                else
                {
                    sw.Write(dr[i].ToString().Trim());
                }
            }
            if (i < dtDataTable.Columns.Count - 1)
            {
                sw.Write(",");
            }
        }
        sw.Write(sw.NewLine);
    }
    sw.Close();
}
Ghebrehiywet
quelle
1

Möglicherweise ist der einfachste Weg zu verwenden:

https://github.com/ukushu/DataExporter

/r/nDies gilt insbesondere für Daten von Datentabellen, die Zeichen oder Trennzeichen in Ihren dataTable-Zellen enthalten. Fast alle anderen Antworten funktionieren mit solchen Zellen nicht.

Sie müssen lediglich den folgenden Code schreiben:

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

var columnNames = dt.Columns.Cast<DataColumn>().
    Select(column => column.ColumnName).ToArray();

csv.AddRow(columnNames);

foreach (DataRow row in dt.Rows)
{
    var fields = row.ItemArray.Select(field => field.ToString()).ToArray;
    csv.AddRow(fields);   
}

csv.Save();
Andrew
quelle
0

Für den Fall, dass jemand anderes darauf stößt , habe ich File.ReadAllText verwendet , um CSV-Daten abzurufen . Dann habe ich sie geändert und mit File.WriteAllText zurückgeschrieben . Die \ r \ n CRLFs waren in Ordnung, aber die \ t-Registerkarten wurden beim Öffnen in Excel ignoriert. (Alle Lösungen in diesem Thread verwenden bisher ein Komma-Trennzeichen, aber das spielt keine Rolle.) Notepad zeigte in der resultierenden Datei das gleiche Format wie in der Quelle. Ein Diff zeigte sogar die Dateien als identisch an. Aber ich habe einen Hinweis bekommen, als ich die Datei in Visual Studio mit einem Binäreditor geöffnet habe. Die Quelldatei war Unicode, aber das Ziel war ASCII . Um dies zu beheben, habe ich sowohl ReadAllText als auch WriteAllText mit dem dritten Argument als System.Text.Encoding.Unicode geändert und von dort aus konnte Excel die aktualisierte Datei öffnen.

TonyG
quelle
0

EJR

private string ExportDatatableToCSV(DataTable dtTable)
{
    StringBuilder sbldr = new StringBuilder();
    if (dtTable.Columns.Count != 0)
    {
        foreach (DataColumn col in dtTable.Columns)
        {
            sbldr.Append(col.ColumnName + ',');
        }
        sbldr.Append("\r\n");
        foreach (DataRow row in dtTable.Rows)
        {
            foreach (DataColumn column in dtTable.Columns)
            {
                sbldr.Append(row[column].ToString() + ',');
            }
            sbldr.Append("\r\n");
        }
    }
    return sbldr.ToString();
}
Julia
quelle
0

Hier ist meine Lösung, basierend auf früheren Antworten von Paul Grimshaw und Anthony VO . Ich habe die eingereicht Code in einem C # -Projekt auf Github .

Mein Hauptbeitrag besteht darin, das explizite Erstellen und Manipulieren von a zu eliminieren StringBuilderund stattdessen nur mit zu arbeiten IEnumerable. Dies vermeidet die Zuweisung eines großen Puffers im Speicher.

public static class Util
{
    public static string EscapeQuotes(this string self) {
        return self?.Replace("\"", "\"\"") ?? "";
    }

    public static string Surround(this string self, string before, string after) {
        return $"{before}{self}{after}";
    }

    public static string Quoted(this string self, string quotes = "\"") {
        return self.Surround(quotes, quotes);
    }

    public static string QuotedCSVFieldIfNecessary(this string self) {
        return (self == null) ? "" : self.Contains('"') ? self.Quoted() : self; 
    }

    public static string ToCsvField(this string self) {
        return self.EscapeQuotes().QuotedCSVFieldIfNecessary();
    }

    public static string ToCsvRow(this IEnumerable<string> self){
        return string.Join(",", self.Select(ToCsvField));
    }

    public static IEnumerable<string> ToCsvRows(this DataTable self) {          
        yield return self.Columns.OfType<object>().Select(c => c.ToString()).ToCsvRow();
        foreach (var dr in self.Rows.OfType<DataRow>())
            yield return dr.ItemArray.Select(item => item.ToString()).ToCsvRow();
    }

    public static void ToCsvFile(this DataTable self, string path) {
        File.WriteAllLines(path, self.ToCsvRows());
    }

}}

Dieser Ansatz kombiniert gut mit der Umwandlung IEnumerablezu Datatable wie hier gefragt .

cdiggins
quelle
0
        DataTable dt = yourData();
        StringBuilder csv = new StringBuilder();
        int dcCounter = 0;

        foreach (DataColumn dc in dt.Columns)
        {
            csv.Append(dc);
            if (dcCounter != dt.Columns.Count - 1)
            {
                csv.Append(",");
            }
            dcCounter++;
        }
        csv.AppendLine();

        int numOfDc = dt.Columns.Count;
        foreach (DataRow dr in dt.Rows)
        {
            int colIndex = 0;
            while (colIndex <= numOfDc - 1)
            {
                var colVal = dr[colIndex].ToString();
                if (colVal != null && colVal != "")
                {
                    DateTime isDateTime;
                    if (DateTime.TryParse(colVal, out isDateTime))
                    {
                        csv.Append(Convert.ToDateTime(colVal).ToShortDateString());
                    }
                    else
                    {
                        csv.Append(dr[colIndex]);
                    }
                }
                else
                {
                    csv.Append("N/A");
                }
                if (colIndex != numOfDc - 1)
                {
                    csv.Append(",");
                }
                colIndex++;
            }
            csv.AppendLine();

Ich musste auch einige Daten überschreiben, weshalb es einige "if else" -Anweisungen gibt. Ich musste sicherstellen, dass ein Feld, das leer war, um stattdessen "N / A" einzugeben, oder wenn ein Datumsfeld als "01/01/1900: 00" formatiert wurde, als "01/01/1900" gespeichert wird. stattdessen.

BondAddict
quelle
0
StringBuilder sb = new StringBuilder();

        foreach (DataColumn col in table.Columns)
        {
            sb.Append(col.ColumnName + ";");
        }

        foreach (DataRow row in table.Rows)
        {
            sb.AppendLine();
            foreach (DataColumn col in table.Columns)
            {
                sb.Append($@"{Convert.ToString(row[col])}" + ";");
            }
        }
        File.WriteAllText(path, sb.ToString());
Inuria
quelle
-1

Wenn sich alle Daten noch in der ersten Zelle befinden, bedeutet dies, dass die Anwendung, mit der Sie die Datei geöffnet haben, ein anderes Trennzeichen erwartet. MSExcel kann das Komma als Trennzeichen behandeln, sofern Sie nichts anderes angegeben haben.

Akram Agbarya
quelle