如何将 DataTable 转换为 CSV?

137

请问为什么以下代码无法工作。数据被保存到了csv文件中,但是数据没有分隔。它们全部存在于每行的第一个单元格中。

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());
感谢。

您可以查看此链接:https://gist.github.com/riyadparvez/4467668 - user
我开发了高性能扩展。请查看此答案 - Nigje
19个回答

4

阅读 这篇文章这篇文章 了解更多信息。


最佳实现方案如下:

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

3

这里是对vc-74帖子的改进,处理逗号的方式与Excel相同。如果数据中包含逗号,Excel会在数据周围加上引号,但如果数据中没有逗号,则不加引号。

    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
为了模拟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();
}

2
这是我的解决方案,基于之前 Paul GrimshawAnthony VO 的回答。 我已经在 Github 上提交了一个 C# 项目的代码
我的主要贡献是消除显式创建和操作 StringBuilder,而是仅使用 IEnumerable。这避免了在内存中分配大缓冲区的情况。
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.Contains('\r') || self.Contains('\n') || 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());
    }
}

这种方法很好地结合了将 IEnumerable 转换为 DataTable 的方法,具体请参考这里的问题

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

大多数现有的答案很容易导致OutOfMemoryException,因此我决定编写自己的答案。

不要这样做:

使用DataSet+StringBuilder会使数据一次性占用3倍的内存:

  1. 将所有数据加载到DataSet
  2. 复制所有数据到StringBuilder
  3. 使用StringBuilder.ToString()将数据复制到字符串中;

相反,您应该将每行记录单独写入FileStream。没有必要在内存中创建整个CSV文件。

更好的方法是使用DataReader而不是DataSet。这样,您可以逐个读取数据库中的数十亿条记录,并逐个将其写入文件。

如果您不介意使用外部库来处理CSV文件,我可以推荐最受欢迎的CsvHelper,它没有依赖关系。

using (var writer = new FileWriter("test.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{       
    foreach (DataColumn dc in dt.Columns)
    {           
        csv.WriteField(dc.ColumnName);
    }
    csv.NextRecord();
    
    foreach (DataRow dr in dt.Rows)
    {           
        foreach (DataColumn dc in dt.Columns)
        {
            csv.WriteField(dr[dc]);
        }
        csv.NextRecord();
    }

    writer.ToString().Dump();
}

1
可能最简单的方法是使用:

https://github.com/ukushu/DataExporter

特别是在数据表中包含/r/n字符或分隔符号的情况下。几乎所有其他答案都无法处理这样的单元格。

您只需要编写以下代码:

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.1.0” 包具有“DotnetTool”包类型,该类型不受“LoyaltyCard”项目支持。 - Zeeshan Ahmad Khalil
将CSV.cs文件复制到您的项目中,并使用VisualBasic函数导入默认的VS库。 - Andrew_STOP_RU_WAR_IN_UA
注意:对于较大的数据集,这仍然会消耗大量内存,并且很容易引起“OutOfMemoryException”。 - Liero
您可以尝试使用包装在DataExporter中的原始VB库。但是(!)原始VB库也不能处理真正巨大的文件-存在某些限制。 - Andrew_STOP_RU_WAR_IN_UA

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

欢迎来到StackOverflow!当答案包含代码片段的描述时,它们是最好的。我个人发现,当问题和答案之间的变量名称对齐时,它们对我更有帮助。 - AWinkle

0

如果还有其他人遇到这个问题,我使用File.ReadAllText来获取CSV数据,然后进行修改并使用File.WriteAllText将其写回。在Excel打开时,\r\n换行符是正常的,但\t制表符被忽略了。(目前在这个帖子中的所有解决方案都使用逗号分隔符,但这并不重要。)记事本显示的结果文件格式与源文件相同。甚至通过Diff工具显示这两个文件完全一样。但是当我使用二进制编辑器在Visual Studio中打开文件时,我得到了一个线索。源文件是Unicode编码,而目标文件是ASCII编码。为了解决这个问题,我修改了ReadAllText和WriteAllText两个方法的第三个参数,将其设置为System.Text.Encoding.Unicode,从那以后Excel能够成功打开更新后的文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接