c # databile in csv


113

Qualcuno potrebbe dirmi perché il seguente codice non funziona. I dati vengono salvati nel file csv, tuttavia i dati non vengono separati. Esiste tutto nella prima cella di ogni riga.

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

Grazie.


Puoi controllare questo gist.github.com/riyadparvez/4467668
utente

Ho sviluppato Extention ad alte prestazioni. controlla questa risposta
Nigje

Risposte:


229

La seguente versione più breve si apre bene in Excel, forse il tuo problema era la virgola finale

.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

E come ha sottolineato Tim, se sei su .net> = 4, puoi renderlo ancora più breve:

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

Come suggerito da Christian, se vuoi gestire caratteri speciali che escono nei campi, sostituisci il blocco loop con:

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

E ultimo suggerimento, potresti scrivere il contenuto csv riga per riga invece che come un intero documento, per evitare di avere un grosso documento in memoria.


2
Non è necessario copiare il ItemArrayin un nuovo String[], è possibile omettere .ToArray()con .NET 4 e utilizzare l' String.Joinoverload che accetta un IEnumerable<T>(modificato).
Tim Schmelter

2
@TimSchmelter, sì ma questi sovraccarichi sono stati introdotti in .net4, il codice non verrà compilato se l'OP utilizza .net <4
vc 74

18
Questo metodo non tiene conto di una virgola all'interno di un valore di colonna.
Christian

2
Invece IEnumerable <string> fields = row.ItemArray.Select (field => field.ToString (). Replace ("\" "," \ "\" ")); sb.AppendLine (" \ "" + string.Join ("\", \ "", campi) + "\" ");
Cristiano

2
@ Si8 Cosa intendi? Questa risposta utilizza solo componenti db ed &nbspè tipica dei documenti HTML / XML. Non è il codice sopra che lo produce a meno che la tabella non contenga &nbsp;esplicitamente
vc 74

36

L'ho racchiuso in una classe di estensione, che ti consente di chiamare:

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

su qualsiasi DataTable.

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

24

Una nuova funzione di estensione basata sulla risposta di Paul Grimshaw. L'ho ripulito e ho aggiunto la possibilità di gestire dati imprevisti. (Dati vuoti, virgolette incorporate e virgole nei titoli ...)

Restituisce anche una stringa che è più flessibile. Restituisce Null se l'oggetto tabella non contiene alcuna struttura.

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

Lo chiami come segue:

var csvData = dataTableOject.ToCsv();

1
Questo è il migliore degli altri qui. Molto bene. Grazie
Fandango68

Fantastica soluzione. Commenti aggiunti a livello locale, ma è stato in grado di utilizzare fuori dagli schemi senza dover scalare la montagna. Grazie.
j.hull

Mi è piaciuto moltissimo! L'ho usato come metodo non statico e ho appena passato il mio DataTable come parametro. Ha funzionato alla grande, grazie.
Kid Koder

9

Se il codice chiamante fa riferimento System.Windows.Formsall'assembly, è possibile considerare un approccio radicalmente diverso. La mia strategia è quella di utilizzare le funzioni già fornite dal framework per ottenere ciò in pochissime righe di codice e senza dover scorrere colonne e righe. Quello che fa il codice seguente è creare a livello di codice un file DataGridViewal volo e impostare DataGridView.DataSourceil file DataTable. Successivamente, seleziono programmaticamente tutte le celle (inclusa l'intestazione) in DataGridViewe chiamo DataGridView.GetClipboardContent(), inserendo i risultati in Windows Clipboard. Quindi, "incollo" il contenuto degli appunti in una chiamata a File.WriteAllText(), assicurandomi di specificare la formattazione di "incolla" come TextDataFormat.CommaSeparatedValue.

Ecco il codice:

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

Si noti che mi assicuro anche di preservare il contenuto degli appunti prima di iniziare e di ripristinarlo una volta terminato, in modo che l'utente non riceva un mucchio di spazzatura imprevista la prossima volta che l'utente tenta di incollare. Le avvertenze principali a questo approccio sono 1) La tua classe deve fare riferimento System.Windows.Forms, il che potrebbe non essere il caso in un livello di astrazione dei dati, 2) Il tuo assembly dovrà essere mirato al framework .NET 4.5, poiché DataGridView non esiste in 4.0, e 3) Il metodo fallirà se gli appunti vengono utilizzati da un altro processo.

Ad ogni modo, questo approccio potrebbe non essere adatto alla tua situazione, ma è comunque interessante e può essere un altro strumento nella tua cassetta degli attrezzi.


1
l'utilizzo degli Appunti non è necessario stackoverflow.com/questions/40726017/… . .GetClipboardContentgestisce anche pochi casi limite di valori che contengono ,. ", \t(converte tabulazione in spazio)
Slai

2
Questo va bene, ma cosa succede se qualcuno utilizza la macchina allo stesso tempo e inserisce qualcosa negli Appunti nel momento critico.
Ayo Adesina

7

L'ho fatto di recente ma ho incluso virgolette doppie attorno ai miei valori.

Ad esempio, modifica queste due righe:

sb.Append("\"" + col.ColumnName + "\","); 
...
sb.Append("\"" + row[i].ToString() + "\","); 

Grazie per il suggerimento, ma tutti i dati sono ancora nella prima cella di ogni riga?
Darren Young

7

Prova a cambiare sb.Append(Environment.NewLine);in 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());

Ciò darà quindi due ritorni di carraige.
Darren Young

@alexl: Questo è quello con cui stavo originariamente, ma era fuori di testa fino a quando VS non si è acceso: o)
Neil Knight

5

Prova a mettere ;invece di,

Spero che sia d'aiuto


5

Leggi questo e questo ?


Sarebbe una migliore implementazione

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

5

L'errore è il separatore di elenco.

Invece di scrivere sb.Append(something... + ',') dovresti mettere qualcosa comesb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);

È necessario inserire il carattere separatore di elenco configurato nel sistema operativo (come nell'esempio sopra) o il separatore di elenco nel computer client in cui verrà visualizzato il file. Un'altra opzione sarebbe configurarlo in app.config o web.config come parametro della tua applicazione.


5

4 righe di codice:

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

Nota che la ToList()fine è importante; Ho bisogno di qualcosa per forzare la valutazione di un'espressione. Se stessi giocando a golf in codice, potrei usarloMin() invece.

Nota anche che il risultato avrà una nuova riga alla fine a causa dell'ultima chiamata a AppendLine(). Potresti non volerlo. Puoi semplicemente chiamare TrimEnd()per rimuoverlo.


3

Ecco un miglioramento al post di vc-74 che gestisce le virgole allo stesso modo di Excel. Excel inserisce virgolette intorno ai dati se i dati hanno una virgola ma non cita se i dati non hanno una virgola.

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

2

Per scrivere su un file, penso che il seguente metodo sia il più efficiente e diretto: (Puoi aggiungere virgolette se vuoi)

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

2

Per imitare Excel CSV:

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

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

Benvenuto in StackOverflow! Le risposte sono migliori quando includono una descrizione dello snippet di codice. Personalmente ho scoperto che quando i nomi delle variabili si allineano tra la domanda e la risposta, sono più utili per me.
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();
}

1

Forse, il modo più semplice sarà usare:

https://github.com/ukushu/DataExporter

specialmente nel caso dei tuoi dati di datatable contenente /r/ncaratteri o simboli separatori all'interno delle celle dataTable.Quasi tutte le altre risposte non funzioneranno con tali cellule.

solo tu devi scrivere il seguente codice:

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

0

Nel caso in cui qualcun altro si imbattesse in questo, stavo usando File.ReadAllText per ottenere dati CSV, quindi l'ho modificato e riscritto con File.WriteAllText . I \ r \ n CRLF andavano bene, ma le schede \ t sono state ignorate quando Excel lo ha aperto. (Tutte le soluzioni in questo thread finora utilizzano un delimitatore di virgola, ma non importa.) Il blocco note mostrava lo stesso formato nel file risultante come nell'origine. Un Diff ha anche mostrato i file come identici. Ma ho avuto un'idea quando ho aperto il file in Visual Studio con un editor binario. Il file di origine era Unicode ma la destinazione era ASCII . Per risolvere, ho modificato sia ReadAllText che WriteAllText con il terzo argomento impostato come System.Text.Encoding.Unicode e da lì Excel è stato in grado di aprire il file aggiornato.


0

FYR

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

0

Ecco la mia soluzione, basata sulle risposte precedenti di Paul Grimshaw e Anthony VO . Ho inviato il codice in un progetto C # su GitHub .

Il mio contributo principale è eliminare la creazione e la manipolazione esplicita di un file StringBuildere lavorare invece solo con IEnumerable. Ciò evita l'allocazione di un grande buffer in memoria.

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

}

Questo approccio si combina perfettamente con la conversione IEnumerablein DataTable come richiesto qui .


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

Avevo anche bisogno di sovrascrivere i dati, motivo per cui ci sono alcune affermazioni "if else". Dovevo assicurarmi che se un campo fosse vuoto per inserire "N / A" o se un campo Data fosse formattato come "01/01/1900: 00", sarebbe stato salvato come "01/01/1900" anziché.


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

-1

se tutti i dati sono ancora nella prima cella, significa che l'applicazione con cui hai aperto il file si aspetta un altro delimitatore. MSExcel può gestire la virgola come delimitatore se non diversamente specificato.

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.