如何将 DataTable 转换为 CSV?

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

谢谢。

302712 次浏览

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

尝试把 ;代替 ,

希望能有帮助

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

例如,更改以下两行:

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

以下较短的版本可以在 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 内容,而不是作为一个整体文档,以避免在内存中有一个大文档。

这个这个


一个更好的实现将是

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

我把这个包装成了一个扩展类,它允许您调用:

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

错误是列表分隔符。

不要写 sb.Append(something... + ','),你应该写一些类似于 sb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);的东西

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

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

下面是 vc-74文章的一个增强,它像 Excel 一样处理逗号。如果数据有逗号,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;
}

如果其他人碰巧发现了这一点,我使用 文件获取 CSV 数据,然后修改它并用 文件写回来。R n CRLF 很好,但是当 Excel 打开它时,t 选项卡被忽略了。(到目前为止,本线程中的所有解决方案都使用逗号分隔符,但这并不重要。)记事本在生成的文件中显示了与源文件中相同的格式。阿迪夫甚至显示文件是完全相同的。但是当我使用二进制编辑器在 VisualStudio 中打开文件时,我得到了一个线索。源文件是 Unicode,但目标是 ASCII。为了解决这个问题,我修改了 ReadAllText 和 WriteAllText,第三个参数设置为 系统。文本。编码。 Unicode,然后 Excel 就可以打开更新后的文件。

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()来删除它。

如果调用代码引用的是 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)如果剪贴板被其他进程使用,该方法将失败。

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

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

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

最简单的方法可能是:

Https://github.com/ukushu/dataexporter

特别是当数据表的数据包含 /r/n字符或分隔符号在 dataTable 单元格内时。几乎所有其他的答案都不适用于这样的单元格。

你只需要写下面的代码:

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

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

这里是我的解决方案,基于以前的答案由 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.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按照这里的要求很好地结合在一起。

大多数现有的答案很容易导致 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();
}