web-dev-qa-db-fra.com

c # datatable to csv

Quelqu'un pourrait-il me dire pourquoi le code suivant ne fonctionne pas? Les données sont enregistrées dans le fichier csv, mais elles ne sont pas séparées. Tout existe dans la première cellule de chaque ligne. 

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

Merci.

93
Darren Young

La version abrégée suivante s'ouvre correctement dans Excel. Peut-être que votre problème était la virgule de fin

.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

Et comme Tim l'a souligné, si vous êtes sur .net> = 4, vous pouvez le rendre encore plus court:

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

Comme suggéré par Christian, si vous souhaitez gérer les caractères spéciaux s’échappant des champs, remplacez le bloc de boucle par:

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

Et dernière suggestion, vous pourriez écrire le contenu csv ligne par ligne au lieu de l’ensemble du document, pour éviter d’avoir un gros document en mémoire.

193
vc 74

Je l'ai encapsulé dans une classe d'extension, qui vous permet d'appeler:

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

sur n'importe quel 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());
    }
}
29
Paul Grimshaw

Une nouvelle fonction d'extension basée sur la réponse de Paul Grimshaw. Je l'ai nettoyé et ajouté la possibilité de gérer des données inattendues. (Données vides, citations incorporées et virgules dans les en-têtes ...)

Il renvoie également une chaîne qui est plus flexible. Il renvoie Null si l'objet table ne contient aucune structure.

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

Vous l'appelez comme suit:

var csvData = dataTableOject.ToCsv();
14
AnthonyVO

Si votre code d'appel fait référence à l'assemblage System.Windows.Forms, vous pouvez envisager une approche radicalement différente . Ma stratégie consiste à utiliser les fonctions déjà fournies par le framework pour accomplir cela dans très peu de lignes de code et sans avoir à parcourir en boucle les colonnes et les lignes. Le code ci-dessous crée par programme une DataGridView à la volée et définit le DataGridView.DataSource sur le DataTable. Ensuite, je sélectionne par programme toutes les cellules (y compris l'en-tête) dans la DataGridView et appelle DataGridView.GetClipboardContent(), plaçant les résultats dans la Windows Clipboard. Ensuite, je "colle" le contenu du presse-papiers dans un appel à File.WriteAllText(), en veillant à spécifier le formatage du "coller" sous la forme TextDataFormat.CommaSeparatedValue.

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

Notez que je m'assure également de conserver le contenu du presse-papiers avant de commencer et de le restaurer une fois que j'ai terminé, afin que l'utilisateur ne reçoive pas un tas d'ordures inattendues lors de la prochaine tentative de collage. Les principales réserves de cette approche sont 1) Votre classe doit référencer System.Windows.Forms, ce qui peut ne pas être le cas dans une couche d'abstraction de données. 2) Votre assembly devra être ciblé pour le framework .NET 4.5, car DataGridView n'existe pas dans la version 4.0. et 3) La méthode échouera si le presse-papiers est utilisé par un autre processus.

Quoi qu'il en soit, cette approche peut ne pas convenir à votre situation, mais elle est néanmoins intéressante et peut constituer un autre outil dans votre boîte à outils.

7
Adam White

Essayez de remplacer sb.Append(Environment.NewLine); par 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());
6
Neil Knight

Je l'ai fait récemment, mais j'ai inclus des guillemets doubles autour de mes valeurs.

Par exemple, modifiez ces deux lignes:

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

L'erreur est le séparateur de liste.

Au lieu d'écrire sb.Append(something... + ',') vous devriez mettre quelque chose comme sb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);

Vous devez insérer le caractère séparateur de liste configuré dans votre système d'exploitation (comme dans l'exemple ci-dessus) ou le séparateur de liste sur l'ordinateur client sur lequel le fichier sera lu. Une autre option serait de le configurer dans app.config ou web.config en tant que paramètre de votre application.

5
Martín Delafuente

Essayez de mettre ; au lieu de ,

J'espère que ça aide

5
alexl

Lisez ceci et ceci ?


Une meilleure mise en œuvre serait

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
naveen

4 lignes de code:

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

Notez que le ToList() à la fin est important; J'ai besoin de quelque chose pour forcer une évaluation d'expression. Si je jouais au golf, je pourrais utiliser Min() à la place.

Notez également que le résultat aura une nouvelle ligne à la fin en raison du dernier appel à AppendLine(). Vous ne pouvez pas vouloir cela. Vous pouvez simplement appeler TrimEnd() pour le supprimer.

3
user2023861

Voici une amélioration de la publication de vc-74 qui traite les virgules de la même manière qu'Excel. Excel place des guillemets autour des données si les données comportent une virgule mais ne les cite pas si les données ne le sont pas.

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

Pour imiter 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
James Carter
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());
1
Nam Nguyễn
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
Ghebrehiywet

Pour écrire dans un fichier, je pense que la méthode suivante est la plus efficace et la plus simple: (Vous pouvez ajouter des guillemets si vous le souhaitez)

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

Peut-être, le moyen le plus facile sera d'utiliser:

https://github.com/ukushu/DataExporter

spécialement dans le cas où vos données contiennent des caractères /r/n ou un symbole séparateur à l'intérieur de vos cellules dataTable.

il suffit d’écrire le code suivant:

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

Au cas où quelqu'un trébucherait là-dessus, j'utilisais File.ReadAllText pour obtenir des données CSV, puis je les ai modifiées et réécrites avec File.WriteAllText . Les CRLF étaient corrects, mais les onglets étaient ignorés lorsqu’Excel les ouvrait. (Toutes les solutions de ce fil utilisent jusqu'à présent un délimiteur de virgule, mais cela n'a pas d'importance.) Le Bloc-notes affiche le même format dans le fichier résultant que dans le source. Un Diff a même montré que les fichiers étaient identiques. Mais j'ai eu un indice lorsque j'ai ouvert le fichier dans Visual Studio avec un éditeur binaire. Le fichier source était Unicode mais la cible étaitASCII. Pour résoudre ce problème, j'ai modifié ReadAllText et WriteAllText avec le troisième argument défini comme System.Text.Encoding.Unicode , et à partir de là, Excel a pu ouvrir le fichier mis à jour.

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

J'avais également besoin de redéfinir les données, c'est pourquoi il y a quelques déclarations "if else". Je devais m'assurer que si un champ était vide, indiquez plutôt "N/A", ou si un champ Date était au format "01/01/1900: 00", il serait enregistré sous le nom "01/01/1900". au lieu.

0
BondAddict

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
Julia

Voici ma solution, basée sur les réponses précédentes de Paul Grimshaw et Anthony VO . J'ai soumis le code dans un projet C # sur Github

Ma principale contribution est d'éliminer explicitement la création et la manipulation d'une StringBuilder et de ne travailler qu'avec IEnumerable. Cela évite l'allocation d'un grand tampon en mémoire. 

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

}

Cette approche se combine bien avec la conversion de IEnumerable en DataTable comme demandé ici

0
cdiggins