C# SQL转CSV程序陷入循环且未生成含数据文件问题排查
问题分析与修复方案
核心问题排查
- 无限循环触发:外层
while (reader.HasRows)循环条件错误。DbDataReader.HasRows仅标识初始是否存在数据,读取过程中不会自动变为false,哪怕读完所有数据,该值仍保持初始状态,导致循环无限执行。 - 文件名记录错误:
filesCreated.Add(filePattern)添加的是文件名模板,而非实际生成的curFileName,导致返回的文件列表无效。 - 多余的流操作:
_exportToSplittedCsv中手动调用writer.Close(),但StreamWriter已被using包裹,using块会自动释放资源,手动关闭可能引发流操作异常。 - SQL硬编码问题:查询语句中的表名固定为
table,未使用传入的tableName参数,无法按指定表导出数据。 - 等待时间不足:Main方法中
task.Wait(360)仅等待360毫秒,程序可能未完成执行就被终止,导致文件生成不完整或未生成。
修复后完整代码
namespace csv1 { class A { public static async Task<List<string>> sql_to_csv(string connectionString, string providerName, string tableName, string outputFilepath, Encoding outputFileEncoding, long maxNbOfRows, bool addHeaderRow = true, string separator = ";") { DbProviderFactory factory = DbProviderFactories.GetFactory(providerName); using (DbConnection connection = factory.CreateConnection()) { connection.ConnectionString = connectionString; await connection.OpenAsync(); DbCommand command = connection.CreateCommand(); // 替换硬编码表名,使用传入的tableName参数 command.CommandText = $"SELECT top 10 * FROM {tableName}"; long totalRecords = 0; long fileCount = 0; List<string> filesCreated = new List<string>(); string filePattern = calculateFilePattern(outputFilepath); using (var reader = await command.ExecuteReaderAsync()) { if (!reader.HasRows) return filesCreated; // 表为空时直接返回空列表 bool hasMoreData = true; // 用实际读取行数判断是否还有数据,避免无限循环 while (hasMoreData) { string curFileName = string.Format(filePattern, ++fileCount); using (var writer = new System.IO.StreamWriter(curFileName, false, outputFileEncoding)) { long rowsWritten = await _exportToSplittedCsv(reader, writer, maxNbOfRows, addHeaderRow, separator); totalRecords += rowsWritten; // 若本次写入行数小于最大限制,说明已读完所有数据 hasMoreData = rowsWritten == maxNbOfRows; } filesCreated.Add(curFileName); // 添加实际生成的文件名 } } return filesCreated; } } private static string calculateFilePattern(string path) { if (path.Contains("{0")) return path; int extIndex = path.LastIndexOf('.'); if (extIndex == -1) return path + "{0:00000}"; else return path.Substring(0, extIndex) + "{0:00000}" + path.Substring(extIndex); } private static async Task<long> _exportToSplittedCsv(System.Data.Common.DbDataReader reader, System.IO.StreamWriter writer, long maxNbOfRows, bool addHeaderRow, string separator) { long rowCount = 0; if (addHeaderRow) { await writer.WriteAsync(getColNames(reader, separator)); await writer.WriteLineAsync(); } // 读取数据直到读完或达到最大行数限制 while (await reader.ReadAsync() && (maxNbOfRows < 1 || rowCount < maxNbOfRows)) { await writer.WriteLineAsync(buildCsvRow(reader, separator)); ++rowCount; } await writer.FlushAsync(); // 移除手动Close,由using块自动处理资源释放 return rowCount; } private static string getColNames(System.Data.Common.DbDataReader reader, string separator) { var rowBuilder = new StringBuilder(); for (int i = 0; i < reader.FieldCount; i++) { rowBuilder.Append(separator).Append(EscapeCsvValue(reader.GetName(i))); } return rowBuilder.Remove(0, 1).ToString(); } private static string buildCsvRow(System.Data.Common.DbDataReader reader, string separator) { var rowBuilder = new StringBuilder(); for (int i = 0; i < reader.FieldCount; i++) { object value = reader.GetValue(i); string stringValue = reader.IsDBNull(i) ? string.Empty : value.ToString(); rowBuilder.Append(separator).Append(EscapeCsvValue(stringValue)); } return rowBuilder.Remove(0, 1).ToString(); } // 新增CSV值转义方法,处理特殊字符避免格式错误 private static string EscapeCsvValue(string value) { if (value.Contains("\"") || value.Contains(separator) || value.Contains("\n") || value.Contains("\r")) { return $"\"{value.Replace("\"", "\"\"")}\""; } return value; } static void Main(string[] args) { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(); builder.DataSource = "xxxxx"; builder.InitialCatalog = "xxxxxx"; builder.IntegratedSecurity = true; string provider = "System.Data.SqlClient"; Task<List<string>> task = sql_to_csv(builder.ConnectionString, provider, "table", "C:\\export_customerOrders{0:00000}.csv", Encoding.Default, 5, true, ","); // 等待任务完成,而非固定短时间等待 task.Wait(); // 输出生成的文件路径,便于验证结果 foreach (var file in task.Result) { Console.WriteLine($"已生成文件:{file}"); } } } }
额外优化说明
- 添加
EscapeCsvValue方法,处理CSV格式中的特殊字符(引号、分隔符、换行等),避免生成的文件格式失效。 - 在
StreamWriter构造中传入指定编码参数,确保文件使用预设编码生成。 - 通过实际写入行数判断是否还有剩余数据,彻底解决无限循环问题。
- Main方法中添加文件生成结果输出,方便快速验证执行效果。
内容的提问来源于stack exchange,提问作者Srini
相关产品推荐
相关产品推荐

