如何将 DataTable 转换为 CSV?

发布于 2024-10-17 00:02:14 字数 501 浏览 3 评论 0原文

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

Could somebody please tell me why the following code is not working. The data is saved into the csv file, however the data is not separated. It all exists within the first cell of each row.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(19

扭转时空 2024-10-24 00:02:14

以下较短的版本在 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

正如蒂姆指出的,如果您使用 .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内容而不是作为整个文档,以避免内存中出现大文档。

The following shorter version opens fine in Excel, maybe your issue was the trailing comma

.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

And as Tim pointed out, if you are on .net>=4, you can make it even shorter:

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

As suggested by Christian, if you want to handle special characters escaping in fields, replace the loop block by:

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

And last suggestion, you could write the csv content line by line instead of as a whole document, to avoid having a big document in memory.

年华零落成诗 2024-10-24 00:02:14

调用:

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

我将其包装到一个扩展类中,它允许您在任何 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());
    }
}

I wrapped this up into an extension class, which allows you to call:

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

on any 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());
    }
}
剩余の解释 2024-10-24 00:02:14

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

A new extension function based on Paul Grimshaw's answer. I cleaned it up and added the ability to handle unexpected data. (Empty Data, Embedded Quotes, and comma's in the headings...)

It also returns a string which is more flexible. It returns Null if the table object does not contain any 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();
    }

You call it as follows:

var csvData = dataTableOject.ToCsv();
你的他你的她 2024-10-24 00:02:14

如果您的调用代码引用 System.Windows.Forms 程序集,您可能会考虑采用完全不同的方法。
我的策略是使用框架已经提供的功能,以很少的代码行来完成此任务,而无需循环遍历列和行。下面的代码所做的是以编程方式动态创建一个 DataGridView 并将 DataGridView.DataSource 设置为 DataTable。接下来,我以编程方式选择 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) 如果剪贴板正被另一个进程使用,该方法将失败。

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

If your calling code is referencing the System.Windows.Forms assembly, you may consider a radically different approach.
My strategy is to use the functions already provided by the framework to accomplish this in very few lines of code and without having to loop through columns and rows. What the code below does is programmatically create a DataGridView on the fly and set the DataGridView.DataSource to the DataTable. Next, I programmatically select all the cells (including the header) in the DataGridView and call DataGridView.GetClipboardContent(), placing the results into the Windows Clipboard. Then, I 'paste' the contents of the clipboard into a call to File.WriteAllText(), making sure to specify the formatting of the 'paste' as TextDataFormat.CommaSeparatedValue.

Here is the 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);
        }
    }
}

Notice I also make sure to preserve the contents of the clipboard before I begin, and restore it once I'm done, so the user does not get a bunch of unexpected garbage next time the user tries to paste. The main caveats to this approach is 1) Your class has to reference System.Windows.Forms, which may not be the case in a data abstraction layer, 2) Your assembly will have to be targeted for .NET 4.5 framework, as DataGridView does not exist in 4.0, and 3) The method will fail if the clipboard is being used by another process.

Anyways, this approach may not be right for your situation, but it is interesting none the less, and can be another tool in your toolbox.

好听的两个字的网名 2024-10-24 00:02:14

我最近这样做了,但在我的值周围添加了双引号。

例如,更改这两行:

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

I did this recently but included double quotes around my values.

For example, change these two lines:

sb.Append("\"" + col.ColumnName + "\","); 
...
sb.Append("\"" + row[i].ToString() + "\","); 
べ映画 2024-10-24 00:02:14

尝试将 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());

Try changing sb.Append(Environment.NewLine); to 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());
她比我温柔 2024-10-24 00:02:14

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() 即可将其删除。

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

Note that the ToList() at the end is important; I need something to force an expression evaluation. If I was code golfing, I could use Min() instead.

Also note that the result will have a newline at the end because of the last call to AppendLine(). You may not want this. You can simply call TrimEnd() to remove it.

吃兔兔 2024-10-24 00:02:14

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

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

To write to a file, I think the following method is the most efficient and straightforward: (You can add quotes if you want)

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));
        }
    }
}
睫毛上残留的泪 2024-10-24 00:02:14

尝试使用 ; 而不是 ,

希望有帮助

Try to put ; instead of ,

Hope it helps

梦旅人picnic 2024-10-24 00:02:14

错误是列表分隔符。

不要编写 sb.Append(something... + ',') 你应该写类似 sb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo. ListSeparator);

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

The error is the list separator.

Instead of writing sb.Append(something... + ',') you should put something like sb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);

You must put the list separator character configured in your operating system (like in the example above), or the list separator in the client machine where the file is going to be watched. Another option would be to configure it in the app.config or web.config as a parammeter of your application.

〃温暖了心ぐ 2024-10-24 00:02:14

阅读这个


A better implementation would be

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

Read this and this?


A better implementation would be

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());
情痴 2024-10-24 00:02:14

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

To mimic 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();
}
风和你 2024-10-24 00:02:14

这是对 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;
    }

Here is an enhancement to vc-74's post that handles commas the same way Excel does. Excel puts quotes around data if the data has a comma but doesn't quote if the data doesn't have a comma.

    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;
    }
星軌x 2024-10-24 00:02:14

这是我的解决方案,基于 Paul Grimshaw安东尼·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 完美结合

Here is my solution, based on previous answers by Paul Grimshaw and Anthony VO.
I've submitted the code in a C# project on Github.

My main contribution is to eliminate explicitly creating and manipulating a StringBuilder and instead working only with IEnumerable. This avoids the allocation of a big buffer in memory.

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

This approach combines nicely with converting IEnumerable to DataTable as asked here.

我的奇迹 2024-10-24 00:02:14
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());
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());
佼人 2024-10-24 00:02:14
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();
}
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();
}
送你一个梦 2024-10-24 00:02:14

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

https://github.com/ukushu/DataExporter

< strong>尤其是在数据表的数据在数据表单元格内包含 /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();

Possibly, most easy way will be to use:

https://github.com/ukushu/DataExporter

especially in case of your data of datatable containing /r/n characters or separator symbol inside of your dataTable cells. Almost all of other answers will not work with such cells.

only you need is to write the following code:

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();
一张白纸 2024-10-24 00:02:14

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

不要这样做:

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

  1. 将所有数据加载到 DataSet
  2. 将所有数据复制到 StringBuilder< /code>
  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();
}

Most existing answers can easily cause OutOfMemoryException, so I decided to write my own answer.

DON' T DO THIS:

using a DataSet + StringBuilder causes the data to occupy the memory 3x at once:

  1. Load All Data into DataSet
  2. Copy all data into StringBuilder
  3. Copy the data to string using StringBuilder.ToString();

Instead you should write each row to a FileStream separately. There is no need to create the whole CSV in memory.

Even better, use a DataReader instead DataSet. That way you can read from database billions of records one by one a write the to a file one by one.

If you don't mind using an external library for CSV, I can recommend the most popular CsvHelper, which has no dependencies.

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();
}
败给现实 2024-10-24 00:02:14

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

In case anyone else stumbles on this, I was using File.ReadAllText to get CSV data and then I modified it and wrote it back with File.WriteAllText. The \r\n CRLFs were fine but the \t tabs were ignored when Excel opened it. (All solutions in this thread so far use a comma delimiter but that doesn't matter.) Notepad showed the same format in the resulting file as in the source. A Diff even showed the files as identical. But I got a clue when I opened the file in Visual Studio with a binary editor. The source file was Unicode but the target was ASCII. To fix, I modified both ReadAllText and WriteAllText with third argument set as System.Text.Encoding.Unicode, and from there Excel was able to open the updated file.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文