带有列名的 DataReader 到 .CSV
Posted
技术标签:
【中文标题】带有列名的 DataReader 到 .CSV【英文标题】:DataReader to .CSV with column names 【发布时间】:2015-06-16 04:00:42 【问题描述】:我正在从 SqlDataReader 生成一个 csv 文件,但是它没有写入列名,我怎样才能让它写入它们?我使用的代码如下:
SqlConnection conn = new SqlConnection(myconn);
SqlCommand cmd = new SqlCommand("dbo.test", conn);
cmd.CommandType = CommandType.StoredProcedure;
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
StringBuilder sb = new StringBuilder();
StreamWriter sw = new StreamWriter(myfilePath + "testfile.csv");
while (reader.Read())
for (int i = 0; i < reader.FieldCount; i++)
string value = reader[i].ToString();
if (value.Contains(","))
value = "\"" + value + "\"";
sb.Append(value.Replace(Environment.NewLine, " ") + ",");
sb.Length--; // Remove the last comma
sb.AppendLine();
conn.Close();
sw.Write(sb.ToString());
sw.Close();
【问题讨论】:
***.com/questions/681653/…的可能重复 【参考方案1】:您可以使用SqlDataReader.GetName
方法获取列的名称,如下所示:
for(int i = 0; i < reader.FieldCount; i++)
string columnName = reader.GetName(i);
【讨论】:
【参考方案2】:读取所有列名并将其附加到sb
,然后迭代阅读器。
SqlDataReader reader = cmd.ExecuteReader();
StringBuilder sb = new StringBuilder();
//Get All column
var columnNames = Enumerable.Range(0, reader.FieldCount)
.Select(reader.GetName) //OR .Select("\""+ reader.GetName"\"")
.ToList();
//Create headers
sb.Append(string.Join(",", columnNames));
//Append Line
sb.AppendLine();
while (reader.Read())
....
【讨论】:
我认为如果字段名称中有逗号,这可能会出现一些问题。【参考方案3】:可以使用SqlDataReader.GetName获取列名
for (int i = 0; i < reader.FieldCount; i++)
string columnName = reader.GetName(i);
您还可以创建如下扩展方法:
public static List<string> ToCSV(this IDataReader dataReader, bool includeHeaderAsFirstRow, string separator)
List<string> csvRows = new List<string>();
StringBuilder sb = null;
if (includeHeaderAsFirstRow)
sb = new StringBuilder();
for (int index = 0; index < dataReader.FieldCount; index++)
if (dataReader.GetName(index) != null)
sb.Append(dataReader.GetName(index));
if (index < dataReader.FieldCount - 1)
sb.Append(separator);
csvRows.Add(sb.ToString());
while (dataReader.Read())
sb = new StringBuilder();
for (int index = 0; index < dataReader.FieldCount - 1; index++)
if (!dataReader.IsDBNull(index))
string value = dataReader.GetValue(index).ToString();
if (dataReader.GetFieldType(index) == typeof(String))
//If double quotes are used in value, ensure each are replaced but 2.
if (value.IndexOf("\"") >= 0)
value = value.Replace("\"", "\"\"");
//If separtor are is in value, ensure it is put in double quotes.
if (value.IndexOf(separator) >= 0)
value = "\"" + value + "\"";
sb.Append(value);
if (index < dataReader.FieldCount - 1)
sb.Append(separator);
if (!dataReader.IsDBNull(dataReader.FieldCount - 1))
sb.Append(dataReader.GetValue(dataReader.FieldCount - 1).ToString().Replace(separator, " "));
csvRows.Add(sb.ToString());
dataReader.Close();
sb = null;
return csvRows;
示例:
List<string> rows = null;
using (SqlDataReader dataReader = command.ExecuteReader())
rows = dataReader.ToCSV(includeHeadersAsFirstRow, separator);
dataReader.Close();
【讨论】:
我想我喜欢你的实现并且会使用一些修改(比如,返回一个字符串而不是字符串列表,并且还实现类似的扩展以直接创建文件)。您在生产环境中对这段代码的测试情况如何?【参考方案4】:使用这个solution 我创建了一个扩展。
/// <summary>
///
/// </summary>
/// <param name="reader"></param>
/// <param name="filename"></param>
/// <param name="path">if null/empty will use IO.Path.GetTempPath()</param>
/// <param name="extension">will use csv by default</param>
public static void ToCsv(this IDataReader reader, string filename, string path = null, string extension = "csv")
int nextResult = 0;
do
var filePath = Path.Combine(string.IsNullOrEmpty(path) ? Path.GetTempPath() : path, string.Format("0.1", filename, extension));
using (StreamWriter writer = new StreamWriter(filePath))
writer.WriteLine(string.Join(",", Enumerable.Range(0, reader.FieldCount).Select(reader.GetName).ToList()));
int count = 0;
while (reader.Read())
writer.WriteLine(string.Join(",", Enumerable.Range(0, reader.FieldCount).Select(reader.GetValue).ToList()));
if (++count % 100 == 0)
writer.Flush();
filename = string.Format("0-1", filename, ++nextResult);
while (reader.NextResult());
【讨论】:
嗨,我已经阅读了你的函数,它对创建 csv 非常有帮助。但是,它不考虑包含 " 或 , 的行。它们会破坏 CSV 上的验证。【参考方案5】:我开发了以下高性能扩展
static void Main(string[] args)
SqlConnection sqlCon = new SqlConnection("Removed");
sqlCon.Open();
SqlCommand sqlCmd = new SqlCommand("Select * from Table", sqlCon);
SqlDataReader reader = sqlCmd.ExecuteReader();
string csv=reader.ToCSVHighPerformance(true);
File.WriteAllText("Test.CSV", csv);
reader.Close();
sqlCon.Close();
扩展:
public static string ToCSVHighPerformance(this IDataReader dataReader, bool includeHeaderAsFirstRow = true,
string separator = ",")
DataTable dataTable = new DataTable();
StringBuilder csvRows = new StringBuilder();
string row = "";
int columns ;
try
dataTable.Load(dataReader);
columns= dataTable.Columns.Count;
//Create Header
if (includeHeaderAsFirstRow)
for (int index = 0; index < columns; index++)
row += (dataTable.Columns[index]);
if (index < columns - 1)
row += (separator);
row += (Environment.NewLine);
csvRows.Append(row);
//Create Rows
for (int rowIndex = 0; rowIndex < dataTable.Rows.Count; rowIndex++)
row = "";
//Row
for (int index = 0; index < columns - 1; index++)
string value = dataTable.Rows[rowIndex][index].ToString();
//If type of field is string
if (dataTable.Rows[rowIndex][index] is string)
//If double quotes are used in value, ensure each are replaced by double quotes.
if (value.IndexOf("\"") >= 0)
value = value.Replace("\"", "\"\"");
//If separtor are is in value, ensure it is put in double quotes.
if (value.IndexOf(separator) >= 0)
value = "\"" + value + "\"";
//If string contain new line character
while (value.Contains("\r"))
value = value.Replace("\r", "");
while (value.Contains("\n"))
value = value.Replace("\n", "");
row += value;
if (index < columns - 1)
row += separator;
dataTable.Rows[rowIndex][columns - 1].ToString().ToString().Replace(separator, " ");
row += Environment.NewLine;
csvRows.Append(row);
catch (Exception ex)
throw ex;
return csvRows.ToString();
【讨论】:
您在遍历它们时缺少最后一列。应该是for (int index = 0; index < columns; index++)
而不是 for (int index = 0; index < columns - 1; index++)
抱歉,您不应将此称为高性能解决方案,因为使用 datatable.Load(IDatareader) 这消除了 DataReader 的主要优点 - DataReader 提供了一个无缓冲的数据流,允许程序逻辑来有效地按顺序处理来自数据源的结果。在检索大量数据时,DataReader 是一个不错的选择,因为数据没有缓存在内存中。 MS Docs。这个解决方案在巨大的结果集上会很慢
当你的数据库服务器和iis服务器不相同时,网络传播时间会影响你的性能。如果没有 datatable.Load,每行您只能从数据库服务器获得一行(对于 1000 行,您有 1000* 网络传播时间),但是通过 datatable.Load(IDatareader),对于每个读取请求(读取请求处理的数量由 .加载函数并取决于行大小和网络包大小)你会得到很多行,这个问题减少了网络传播时间。
@Nigje,很抱歉,@Baximilian 就在这里。而你所比较的没有意义。如果我们相信您的前提,也不能保证datatable.Load
也不会逐行获取数据。我认为您没有将 DataTable 用于它的用途:流式传输大量数据并在传入时以谨慎的数据包对其进行处理,而不是在服务器、客户端和网络上施加巨大的负载并将所有数据加载到内存,然后处理它。这可能会产生许多不良的副作用,例如资源争用、锁定、低并发、OOM 等。以上是关于带有列名的 DataReader 到 .CSV的主要内容,如果未能解决你的问题,请参考以下文章
C# 面板上带有 DataReader 和 TextBox 控件的Repeater控件