C#数据表到CSV


113

有人可以告诉我为什么下面的代码不起作用。数据已保存到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());

谢谢。



我开发了高性能Extension。检查这个答案
Nigje

Answers:


229

以下较短的版本在Excel中可以正常打开,也许您的问题是结尾逗号

.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

正如Tim所指出的,如果您使用的是.net> = 4,则可以使其更短:

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

如克里斯蒂安(Christian)所建议,如果您要处理在字段中转义的特殊字符,请用以下方式替换循环块:

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

最后一个建议是,您可以逐行而不是整个文档地编写csv内容,以避免在内存中存储较大的文档。


2
无需将其复制ItemArray到新的文件String[],您可以省略.ToArray().NET 4并使用带有(已编辑)的String.Join重载IEnumerable<T>
蒂姆·施密特

2
@TimSchmelter,是的,但是这些重载是在.net4中引入的,如果OP使用.net <4
vc

18
此方法不考虑列值内的逗号。
基督教徒

2
而是IEnumerable <string>字段= row.ItemArray.Select(field => field.ToString()。Replace(“ \”“,” \“ \”“)); sb.AppendLine(” \“” + string.Join (“ \”,\“”,字段)+“ \”“);
Christian

2
@ Si8是什么意思?该答案仅使用db组件,并且&nbsp是HTML / XML文档的典型代表。除非表格&nbsp;明确包含,否则不是上面的代码产生它
vc 74

36

我将其包装到扩展类中,该类允许您调用:

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

在任何数据表上。

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

基于Paul Grimshaw的答案的新扩展功能。我将其清理并添加了处理意外数据的功能。(标题中为空数据,嵌入式引号和逗号...)

它还返回一个更灵活的字符串。如果表对象不包含任何结构,则返回Null。

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

您将其称为如下:

var csvData = dataTableOject.ToCsv();

1
这是这里最好的。做得好。谢谢
Fandango68 '18

很棒的解决方案。在本地添加了评论,但无需开箱即用即可使用。谢谢。
j.hull

喜欢这个!我将其用作非静态方法,只是将DataTable作为参数传递。很好,谢谢。
Kid Koder

9

如果您的调用代码引用了System.Windows.Forms程序集,则可以考虑使用完全不同的方法。我的策略是使用框架已提供的功能以很少的代码行来完成此任务,而不必遍历列和行。以下代码的作用是以编程方式DataGridView即时创建一个,并将设置DataGridView.DataSourceDataTable。接下来,我以编程方式选择DataGridView和调用中的所有单元格(包括标题)DataGridView.GetClipboardContent(),并将结果放入Windows中Clipboard。然后,我将剪贴板的内容“粘贴”到调用中File.WriteAllText(),确保将“粘贴”的格式指定为TextDataFormat.CommaSeparatedValue

这是代码:

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

请注意,我还要确保在开始之前先保存剪贴板的内容,并在完成后将其还原,这样用户下次尝试粘贴时就不会收到大量意外垃圾。这种方法的主要警告是:1)您的类必须引用System.Windows.Forms(在数据抽象层中可能不是这种情况); 2)您的程序集必须以.NET 4.5框架为目标,因为DataGridView在4.0中不存在, 3)如果剪贴板被另一个进程使用,则该方法将失败。

无论如何,这种方法可能不适合您的情况,但是仍然很有趣,它可以成为工具箱中的另一个工具。


1
不需要使用剪贴板stackoverflow.com/questions/40726017/….GetClipboardContent还处理包含的值的少数边缘情况,"\t(它将制表符转换为空格)
Slai

2
很好,但是如果有人同时使用机器并在关键时刻将某些东西放入剪贴板,该怎么办。
Ayo Adesina'2

7

我最近做了这个,但是在我的价值观周围加上了双引号。

例如,更改这两行:

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

感谢您的建议,但它们的所有数据仍位于每行的第一个单元格内吗?
达伦·扬

7

尝试更改sb.Append(Environment.NewLine);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());

然后,将获得两次偷运回报。
达伦·扬

@alexl:那是我本来要用的,但是直到VS被解雇之前,它才浮出水面:o)
尼尔·奈特


5

阅读这个这个


更好的实现是

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

错误是列表分隔符。

而不是写,sb.Append(something... + ',')你应该像sb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);

您必须将操作系统中配置的列表分隔符字符(如上例中所示)或列表分隔符放置在将要监视文件的客户端计算机中。另一个选择是在app.config或web.config中将其配置为应用程序的参数。


5

4行代码:

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

请注意,ToList()结尾处很重要;我需要一些东西来强制进行表达式评估。如果我是打高尔夫球的代码,则可以Min()改用。

另请注意,由于最后一次调用,结果末尾会有换行符AppendLine()。您可能不想要这个。您只需调用TrimEnd()即可将其删除。


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

2

要写入文件,我认为以下方法是最有效和最直接的:(如果需要,可以添加引号)

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

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

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

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

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

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

0

万一其他人迷失了方向,我使用File.ReadAllText来获取CSV数据,然后我对其进行了修改并使用File.WriteAllText将其写回。\ r \ n CRLF很好,但是Excel打开时,\ t选项卡被忽略了。(到目前为止,该线程中的所有解决方案都使用逗号分隔符,但这无关紧要。)记事本在结果文件中显示的格式与源文件中的格式相同。差异甚至显示文件相同。但是,当我使用二进制编辑器在Visual Studio中打开文件时,我有了一个线索。源文件是Unicode,但目标文件是ASCII。为了解决这个问题,我修改了ReadAllText和WriteAllText,并将第三个参数设置为System.Text.Encoding.Unicode,然后Excel就可以打开更新的文件。


0

财政年度

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

这是我的解决方案,基于Paul GrimshawAnthony VO的先前答案。我已经在Github上的C#项目中提交了代码

我的主要贡献是消除显式创建和操作a 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.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 很好地结合在一起。


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

我还需要做一些数据重写,这就是为什么有一些“ if else”语句的原因。我需要确保如果字段为空而输入“ N / A”,或者如果“日期”字段的格式设置为“ 01/01/1900:00”,则将其另存为“ 01/01/1900”代替。


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

如果所有数据仍在第一个单元格中,则表示打开文件所用的应用程序需要另一个定界符。除非另行指定,否则MSExcel可以将逗号作为定界符。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.