c # datable en csv

113

Quelqu'un pourrait-il me dire pourquoi le code suivant ne fonctionne pas. Les données sont enregistrées dans le fichier csv, mais les données 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.

Darren Young
la source
Vous pouvez consulter ce gist.github.com/riyadparvez/4467668
utilisateur
J'ai développé une extension haute performance. vérifier cette réponse
Nigje

Réponses:

229

La version plus courte 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 dans les 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 pouvez écrire le contenu csv ligne par ligne plutôt que comme un document entier, pour éviter d'avoir un gros document en mémoire.

vc 74
la source
2
Pas besoin de copier le ItemArraydans un nouveau String[], vous pouvez l'omettre .ToArray()avec .NET 4 et utiliser la String.Joinsurcharge qui prend un IEnumerable<T>(édité).
Tim Schmelter
2
@TimSchmelter, oui mais ces surcharges ont été introduites dans .net4, le code ne se compilera pas si l'OP utilise .net <4
vc 74
18
Cette méthode ne prend pas en compte une virgule à l'intérieur d'une valeur de colonne.
Christian
2
Au lieu de cela, IEnumerable <string> fields = row.ItemArray.Select (field => field.ToString (). Replace ("\" "," \ "\" ")); sb.AppendLine (" \ "" + string.Join ("\", \ "", champs) + "\" ");
Christian
2
@ Si8 Que voulez-vous dire? Cette réponse utilise uniquement des composants db et &nbspest typique des documents HTML / XML. Ce n'est pas le code ci-dessus qui le produit à moins que le tableau ne contienne &nbsp;explicitement
vc 74
36

J'ai enveloppé cela 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());
    }
}
Paul Grimshaw
la source
24

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();
AnthonyVO
la source
1
Celui-ci est le meilleur des autres ici. Bien joué. Merci
Fandango68
Solution fantastique. Ajout de commentaires localement, mais a pu utiliser hors de la boîte sans avoir à escalader la montagne. Je vous remercie.
j.hull
J'ai adoré ça! Je l'ai utilisé comme méthode non statique et je viens de passer mon DataTable en tant que paramètre. A très bien fonctionné, merci.
Kid Koder le
9

Si votre code appelant fait référence à l' System.Windows.Formsassembly, vous pouvez envisager une approche radicalement différente. Ma stratégie consiste à utiliser les fonctions déjà fournies par le framework pour y parvenir en très peu de lignes de code et sans avoir à parcourir les colonnes et les lignes. Ce que fait le code ci-dessous est de créer par programme un DataGridViewà la volée et de définir DataGridView.DataSourcele DataTable. Ensuite, je sélectionne par programme toutes les cellules (y compris l'en-tête) dans DataGridViewet appelle DataGridView.GetClipboardContent(), plaçant les résultats dans Windows Clipboard. Ensuite, je `` colle '' le contenu du presse-papiers dans un appel à File.WriteAllText(), en veillant à spécifier le formatage du `` coller '' commeTextDataFormat.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);
        }
    }
}

Remarquez 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 n'obtienne pas un tas de déchets inattendus la prochaine fois que l'utilisateur essaie de coller. Les principales mises en garde à cette approche sont 1) Votre classe doit référencerSystem.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 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 n'est peut-être pas adaptée à votre situation, mais elle n'en est pas moins intéressante et peut être un autre outil dans votre boîte à outils.

Adam White
la source
1
l'utilisation du Presse-papiers n'est pas nécessaire stackoverflow.com/questions/40726017/… . .GetClipboardContentgère également quelques cas extrêmes de valeurs contenant ,. ", \t(il convertit tab en espace)
Slai
2
C'est bien, mais que faire si quelqu'un utilise la machine en même temps et met quelque chose dans le presse-papiers au moment critique.
Ayo Adesina
7

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() + "\","); 
Ben Jakuben
la source
Merci pour la suggestion, mais toutes les données sont-elles toujours dans la première cellule de chaque ligne?
Darren Young
7

Essayez de changer sb.Append(Environment.NewLine);en 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
la source
Cela donnera alors deux retours de carraige.
Darren Young
@alexl: C'est ce que j'allais à l'origine, mais c'était de ma tête jusqu'à ce que VS se déclenche: o)
Neil Knight
5

Essayez de mettre ;au lieu de,

J'espère que ça aide

alexl
la source
5

Lisez ceci et cela ?


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());
Naveen
la source
5

L'erreur est le séparateur de liste.

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

Vous devez placer 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 dans la machine cliente où le fichier va être regardé. Une autre option serait de le configurer dans app.config ou web.config en tant que paramètre de votre application.

Martín Delafuente
la source
5

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 codais le golf, je pourrais utiliser à la Min()place.

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

user2023861
la source
3

Voici une amélioration de la publication de vc-74 qui gère 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 comportent pas de virgule.

    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
la source
2

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));
        }
    }
}
Étudiant222
la source
2

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();
}
James Carter
la source
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
la source
Bienvenue dans StackOverflow! Les réponses sont meilleures lorsqu'elles incluent une description de l'extrait de code. J'ai personnellement trouvé que lorsque les noms de variables s'alignent entre la question et la réponse, ils me sont plus utiles.
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
la source
1

Le moyen le plus simple sera peut-être d'utiliser:

https://github.com/ukushu/DataExporter

en particulier dans le cas de vos données de datatable contenant des /r/ncaractères ou un symbole de séparateur à l'intérieur de vos cellules dataTable.Presque toutes les autres réponses ne fonctionneront pas avec de telles cellules.

il vous 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();
Andrew
la source
0

Au cas où quelqu'un d'autre trébucherait là-dessus, j'utilisais File.ReadAllText pour obtenir des données CSV, puis je les ai modifiées et écrites avec File.WriteAllText . Les CRLF \ r \ n fonctionnaient bien mais les onglets \ t étaient ignorés lorsque Excel l'ouvrait. (Toutes les solutions de ce fil jusqu'à présent utilisent un séparateur de virgule, mais cela n'a pas d'importance.) Le bloc-notes a montré le même format dans le fichier résultant que dans la 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 était ASCII . Pour corriger, 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.

TonyG
la source
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();
}
Julia
la source
0

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'un StringBuilderet de travailler uniquement avec IEnumerable. Cela évite l'allocation d'un gros 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 IEnumerableen DataTable comme demandé ici .

cdiggins
la source
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();

J'avais également besoin de faire un remplacement des données, c'est pourquoi il y a quelques déclarations «if else». Je devais m'assurer que si un champ était vide pour saisir "N / A" à la place, ou si un champ de date était formaté comme "01/01/1900: 00" qu'il serait enregistré comme "01/01/1900" au lieu.

BondAddict
la source
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());
Inurie
la source
-1

si toutes les données sont toujours dans la première cellule, cela signifie que l'application avec laquelle vous avez ouvert le fichier attend un autre délimiteur. MSExcel peut gérer la virgule comme délimiteur, sauf indication contraire de votre part.

Akram Agbarya
la source