将DataTable转换为CSV流

31

目前有一个DataTable,但希望通过WebHandler向用户流式传输它。 FileHelpersCommonEngine.DataTableToCsv(dt, "file.csv")。但是它保存到文件中。我该如何将其保存到流中?我知道当我预先知道列或它们不改变时如何做到这一点,但我想直接从数据表中生成列标题。

如果我知道列,我只需创建类:

[DelimitedRecord(",")]
public class MailMergeFields
{
    [FieldQuoted()]
    public string FirstName;
    [FieldQuoted()]
    public string LastName;
}

然后使用FileHelperEngine并添加记录:

FileHelperEngine engine = new FileHelperEngine(typeof(MailMergeFields));

MailMergeFields[] merge = new MailMergeFields[dt.Rows.Count + 1];

// add headers
merge[0] = new MailMergeFields();
merge[0].FirstName = "FirstName";
merge[0].LastName = "LastName";

int i = 1;              
// add records
foreach (DataRow dr in dt.Rows)
{
    merge[i] = new MailMergeFields();
    merge[i].FirstName = dr["Forename"];
    merge[i].LastName = dr["Surname"];
    i++;
}

最后将内容写入流中:
TextWriter writer = new StringWriter();
engine.WriteStream(writer, merge);
context.Response.Write(writer.ToString());

很不幸,由于我事先不知道列名,因此无法事先创建类。


File Helpers库是开源的。你为什么不深入其中,添加自己的方法呢? - Keltex
您可以查看此链接 https://gist.github.com/riyadparvez/4467668 - user
@user:这个代码片段存在一个错误,逗号分隔的条目将无法被正确处理。请参见https://dev59.com/KXRA5IYBdhLWcg3w9ivq。 - sampathsris
9个回答

69

你可以自己快速地写一些内容:

public static class Extensions
{
    public static string ToCSV(this DataTable table)
    {
        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" : ",");
            }
        }

        return result.ToString();
    }
}

而且进行测试:

  public static void Main()
  {
        DataTable table = new DataTable();
        table.Columns.Add("Name");
        table.Columns.Add("Age");
        table.Rows.Add("John Doe", "45");
        table.Rows.Add("Jane Doe", "35");
        table.Rows.Add("Jack Doe", "27");
        var bytes = Encoding.GetEncoding("iso-8859-1").GetBytes(table.ToCSV());
        MemoryStream stream = new MemoryStream(bytes);

        StreamReader reader = new StreamReader(stream);
        Console.WriteLine(reader.ReadToEnd());
  }

编辑:关于您的评论:

这要取决于您想如何格式化您的csv,但通常如果文本包含特殊字符,则应将其括在双引号中,例如:"my,text"。您可以在创建csv的代码中添加检查以检查是否有特殊字符,并在其周围加上双引号。至于.NET 2.0的问题,只需在类中创建一个帮助方法或在方法声明中去掉“this”一词,并像这样调用它:Extensions.ToCsv(table);


19
如果数据包含引用、换行或逗号,该怎么办? - SamWM
这对我有用。根据我的需求进行了一些调整。Excel将换行显示为?(从SQL Server导出时也有同样的问题)。 - SamWM
建议将以下代码添加到ToCSV函数中:string s = result.ToString(); s = s.TrimEnd(new char[] { '\r', '\n' }); return s; - Cannon

13

更新 1

我已经修改了使用StreamWriter,增加了一个选项来检查是否需要在输出中包含列标题。

public static bool DataTableToCSV(DataTable dtSource, StreamWriter writer, bool includeHeader)
{
    if (dtSource == null || writer == null) return false;

    if (includeHeader)
    {
        string[] columnNames = dtSource.Columns.Cast<DataColumn>().Select(column => "\"" + column.ColumnName.Replace("\"", "\"\"") + "\"").ToArray<string>();
        writer.WriteLine(String.Join(",", columnNames));
        writer.Flush();
    }

    foreach (DataRow row in dtSource.Rows)
    {
        string[] fields = row.ItemArray.Select(field => "\"" + field.ToString().Replace("\"", "\"\"") + "\"").ToArray<string>();
        writer.WriteLine(String.Join(",", fields));
        writer.Flush();
    }

    return true;
}

如您所见,您可以通过初始的 StreamWriter 来选择输出方式。如果您使用 StreamWriter(Stream BaseStream),您可以将csv写入MemoryStream、FileStream等。

原文:

我有一个简单的将datatable转为csv的函数,一直都很好用:

    public static void DataTableToCsv(DataTable dt, string csvFile)
    {
        StringBuilder sb = new StringBuilder();

        var columnNames = dt.Columns.Cast<DataColumn>().Select(column => "\"" + column.ColumnName.Replace("\"", "\"\"") + "\"").ToArray();
        sb.AppendLine(string.Join(",", columnNames));

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

        File.WriteAllText(csvFile, sb.ToString(), Encoding.Default);
    }

3
如果你能将你的datatable转换为IEnumerable,那么这个方法适用于你...
    Response.Clear();
    Response.Buffer = true;

    Response.AddHeader("content-disposition", "attachment;filename=FileName.csv");
    Response.Charset = "";
    Response.ContentType = "application/text";
    Response.Output.Write(ExampleClass.ConvertToCSV(GetListOfObject(), typeof(object)));
    Response.Flush();
    Response.End();



public static string ConvertToCSV(IEnumerable col, Type type)
        {
            StringBuilder sb = new StringBuilder();
            StringBuilder header = new StringBuilder();

            // Gets all  properies of the class
            PropertyInfo[] pi = type.GetProperties();

            // Create CSV header using the classes properties
            foreach (PropertyInfo p in pi)
            {
                header.Append(p.Name + ",");
            }

            sb.AppendLine(header.ToString().Remove(header.Length));

            foreach (object t in col)
            {
                StringBuilder body = new StringBuilder();

                // Create new item
                foreach (PropertyInfo p in pi)
                {
                    object o = p.GetValue(t, null);
                    body.Append(o.ToString() + ",");
                }

                sb.AppendLine(body.ToString().Remove(body.Length));
            }
            return sb.ToString();
        }

1
谢谢,FYI - header.Length和body.Length应该是-1。 - downatone

3
public void CreateCSVFile(DataTable dt, string strFilePath,string separator)
        {               
            #region Export Grid to CSV
            // Create the CSV file to which grid data will be exported.

            StreamWriter sw = new StreamWriter(strFilePath, false); 
            int iColCount = dt.Columns.Count;
            for (int i = 0; i < iColCount; i++)
            {    
                sw.Write(dt.Columns[i]);    
                if (i < iColCount - 1)
                {
                    sw.Write(separator);
                }  
            }    

            sw.Write(sw.NewLine);

            // Now write all the rows.
            foreach (DataRow dr in dt.Rows)
            {
                for (int i = 0; i < iColCount; i++)
                {
                    if (!Convert.IsDBNull(dr[i]))
                    {
                        sw.Write(dr[i].ToString());  
                    }

                    if (i < iColCount - 1)
                    {
                        sw.Write(separator);
                    }
                }
                sw.Write(sw.NewLine);
            }

            sw.Close();
            #endregion
        }

3

我不确定这段代码从VB转换到C#是否正确,但如果你不想在数字周围加引号,可以按照以下方式比较数据类型。

public string DataTableToCSV(DataTable dt)
{
    StringBuilder sb = new StringBuilder();
    if (dt == null)
        return "";

    try {
        // Create the header row
        for (int i = 0; i <= dt.Columns.Count - 1; i++) {
            // Append column name in quotes
            sb.Append("\"" + dt.Columns[i].ColumnName + "\"");
            // Add carriage return and linefeed if last column, else add comma
            sb.Append(i == dt.Columns.Count - 1 ? "\n" : ",");
        }


        foreach (DataRow row in dt.Rows) {
            for (int i = 0; i <= dt.Columns.Count - 1; i++) {
                // Append value in quotes
                //sb.Append("""" & row.Item(i) & """")

                // OR only quote items that that are equivilant to strings
                sb.Append(object.ReferenceEquals(dt.Columns[i].DataType, typeof(string)) || object.ReferenceEquals(dt.Columns[i].DataType, typeof(char)) ? "\"" + row[i] + "\"" : row[i]);

                // Append CR+LF if last field, else add Comma
                sb.Append(i == dt.Columns.Count - 1 ? "\n" : ",");
            }
        }
        return sb.ToString;
    } catch (Exception ex) {
        // Handle the exception however you want
        return "";
    }

}

3

如果您希望在不创建文件的情况下向用户流传递CSV,则我发现以下方法是最简单的。您可以使用任何扩展方法来创建ToCsv()函数(该函数基于给定的DataTable返回一个字符串)。

        var report = myDataTable.ToCsv();
        var bytes = Encoding.GetEncoding("iso-8859-1").GetBytes(report);

        Response.Buffer = true;
        Response.Clear();
        Response.AddHeader("content-disposition", "attachment; filename=report.csv");
        Response.ContentType = "text/csv";
        Response.BinaryWrite(bytes);
        Response.End();

2

我使用了以下代码,从某人的博客中复制而来(请原谅我没有注明出处)。它通过引用每个字段值来优雅地处理引号、换行符和逗号。

    /// <summary>
    /// Converts the passed in data table to a CSV-style string.      
    /// </summary>
    /// <param name="table">Table to convert</param>
    /// <returns>Resulting CSV-style string</returns>
    public static string ToCSV(this DataTable table)
    {
        return ToCSV(table, ",", true);
    }

    /// <summary>
    /// Converts the passed in data table to a CSV-style string.
    /// </summary>
    /// <param name="table">Table to convert</param>
    /// <param name="includeHeader">true - include headers<br/>
    /// false - do not include header column</param>
    /// <returns>Resulting CSV-style string</returns>
    public static string ToCSV(this DataTable table, bool includeHeader)
    {
        return ToCSV(table, ",", includeHeader);
    }

    /// <summary>
    /// Converts the passed in data table to a CSV-style string.
    /// </summary>
    /// <param name="table">Table to convert</param>
    /// <param name="includeHeader">true - include headers<br/>
    /// false - do not include header column</param>
    /// <returns>Resulting CSV-style string</returns>
     public static string ToCSV(this DataTable table, string delimiter, bool includeHeader)
    {
        var result = new StringBuilder();

        if (includeHeader)
        {
            foreach (DataColumn column in table.Columns)
            {
                result.Append(column.ColumnName);
                result.Append(delimiter);
            }

            result.Remove(--result.Length, 0);
            result.Append(Environment.NewLine);
        }

        foreach (DataRow row in table.Rows)
        {
            foreach (object item in row.ItemArray)
            {
                if (item is DBNull)
                    result.Append(delimiter);
                else
                {
                    string itemAsString = item.ToString();
                    // Double up all embedded double quotes
                    itemAsString = itemAsString.Replace("\"", "\"\"");

                    // To keep things simple, always delimit with double-quotes
                    // so we don't have to determine in which cases they're necessary
                    // and which cases they're not.
                    itemAsString = "\"" + itemAsString + "\"";

                    result.Append(itemAsString + delimiter);
                }
            }

            result.Remove(--result.Length, 0);
            result.Append(Environment.NewLine);
        }

        return result.ToString();
    }

2
您可以尝试使用类似以下的方法。在这种情况下,我使用了一个存储过程来获取更多的数据表,并将它们全部使用CSV导出。
using System;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.IO;

namespace bo
{
class Program
{
    static private void CreateCSVFile(DataTable dt, string strFilePath)
    {
        #region Export Grid to CSV
        // Create the CSV file to which grid data will be exported.
        StreamWriter sw = new StreamWriter(strFilePath, false);
        int iColCount = dt.Columns.Count;

        // First we will write the headers.

        //DataTable dt = m_dsProducts.Tables[0];
        for (int i = 0; i < iColCount; i++)
        {
            sw.Write(dt.Columns[i]);
            if (i < iColCount - 1)
            {
                sw.Write(";");
            }
        }
        sw.Write(sw.NewLine);

        // Now write all the rows.
        foreach (DataRow dr in dt.Rows)
        {
            for (int i = 0; i < iColCount; i++)
            {
                if (!Convert.IsDBNull(dr[i]))
                {
                    sw.Write(dr[i].ToString());
                }
                if (i < iColCount -1 )
                {
                    sw.Write(";");
                }
            }
            sw.Write(sw.NewLine);
        }
        sw.Close();

        #endregion
    }
    static void Main(string[] args)
    {
        string strConn = "connection string to sql";
        string direktorij = @"d:";
        SqlConnection conn = new SqlConnection(strConn); 
        SqlCommand command = new SqlCommand("sp_ado_pos_data", conn);
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.Add('@skl_id', SqlDbType.Int).Value = 158;
        SqlDataAdapter adapter = new SqlDataAdapter(command);
        DataSet ds = new DataSet();
        adapter.Fill(ds);
        for (int i = 0; i < ds.Tables.Count; i++)
        {
            string datoteka  = (string.Format(@"{0}tablea{1}.csv", direktorij, i));
            DataTable tabela = ds.Tables[i];
            CreateCSVFile(tabela,datoteka );
            Console.WriteLine("Generišem tabelu {0}", datoteka);
        }
        Console.ReadKey();
    }
  }
}

1
BFree的回答对我有用。我需要将流直接发布到浏览器上。我想这是一种常见的替代方法。我在BFree的Main()代码中添加了以下内容来实现这一点:
//StreamReader reader = new StreamReader(stream);
//Console.WriteLine(reader.ReadToEnd());

string fileName = "fileName.csv";
HttpContext.Current.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
HttpContext.Current.Response.AddHeader("content-disposition", string.Format("attachment;filename={0}", fileName));
stream.Position = 0;
stream.WriteTo(HttpContext.Current.Response.OutputStream);

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