Конвертировать DataTable в поток CSV
В настоящее время есть DataTable, но вы хотите передать его пользователю через WebHandler. FileHelpers имеет CommonEngine.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());
К сожалению, так как я не знаю столбцы заранее, я не могу создать класс заранее.
9 ответов
Вы можете просто написать что-нибудь быстро самостоятельно:
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());
}
РЕДАКТИРОВАТЬ: Re ваши комментарии:
Это зависит от того, как вы хотите отформатировать CSV, но, как правило, если текст содержит специальные символы, вы хотите заключить его в двойные кавычки, например: "my,text". Вы можете добавить проверку в коде, который создает CSV для проверки специальных символов и заключает текст в двойные кавычки, если это так. Что касается.NET 2.0, просто создайте его как вспомогательный метод в вашем классе или удалите слово this в объявлении метода и вызовите его так: Extensions.ToCsv(table);
Обновление 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 в MemeryStream, FileStream и т. Д.
происхождения
У меня есть функция datasatable to 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);
}
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
}
Если вы можете превратить ваши данные в 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();
}
Я не знаю, если это преобразовано из 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 "";
}
}
Если вы хотите транслировать 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();
Вы можете попробовать использовать что-то вроде этого. В этом случае я использовал одну хранимую процедуру, чтобы получить больше таблиц данных и экспортировать их все, используя 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();
}
}
}
Я использовал следующий код, разграбленный из чьего-то блога (прошу прощения за отсутствие цитирования). Он заботится о кавычках, переводе строки и запятой достаточно элегантным способом, заключая в кавычки каждое значение поля.
/// <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();
}
Ответ BFree работал для меня. Мне нужно было опубликовать поток прямо в браузере. То, что я представляю, является обычной альтернативой. Для этого я добавил следующее в код Main() BFree:
//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);