SqlBulkCopy.WriteToServerAsync无法写入SQL Server表,同步方法正常
问题原因分析
- 异步操作未等待:直接调用
WriteToServerAsync或用Task.Run包裹但不等待,导致代码提前执行dataTable.Clear()或_connection.Close(),此时异步写入任务尚未完成,数据被清空或连接被中断,最终无数据写入数据库。 - 同步流程与异步操作不兼容:原代码采用同步循环读取逻辑,异步写入未与读取流程协同,导致资源提前释放。
修复方案:正确实现异步写入
1. 改造方法为异步模式
将整个导入方法声明为async Task,全程使用异步IO操作,确保所有异步任务执行完成后再进行后续步骤。
2. 修正后的核心代码
public async Task ImportCsvAsync(string FilePath, string TargetTable, int BatchSize, int columnsInCsv) { var dataTable = new DataTable(TargetTable); using var streamReader = new StreamReader(FilePath); using var bulkCopy = new SqlBulkCopy(this._connection, SqlBulkCopyOptions.TableLock, null) { DestinationTableName = TargetTable, BulkCopyTimeout = 0, BatchSize = BatchSize, }; // 此处需提前完成DataTable列与SQL表的映射配置 // ... var outputFields = new string[columnsInCsv]; await this._connection.OpenAsync(); string line; int totalRows = 0; int rowCounter = 0; while ((line = await streamReader.ReadLineAsync()) != null) { CsvTools.ParseCsvLineWriteDirect(line, ref outputFields); dataTable.LoadDataRow(outputFields, true); totalRows++; rowCounter++; if (rowCounter >= BatchSize) { try { await bulkCopy.WriteToServerAsync(dataTable); rowCounter = 0; dataTable.Clear(); } catch (Exception ex) { Console.Error.WriteLine(ex.ToString()); return; } } } if (dataTable.Rows.Count > 0) { await bulkCopy.WriteToServerAsync(dataTable); dataTable.Clear(); } await this._connection.CloseAsync(); }
3. 关键修改说明
- 用
await streamReader.ReadLineAsync()替代同步读取,避免阻塞线程,提升文件IO利用率。 - 调用
await bulkCopy.WriteToServerAsync(dataTable)确保数据库写入完成后,再执行清空DataTable或关闭连接的操作。 - 配套使用
OpenAsync和CloseAsync的异步版本,保持全流程异步一致性。
进一步性能优化建议
- 并行解析与写入:使用
Channel<T>实现生产者-消费者模式,一个线程异步解析CSV行并写入Channel,另一个线程从Channel读取数据批量写入数据库,重叠文件IO与数据库IO操作,大幅提升整体导入速度。 - 调整批次大小:根据服务器内存和数据库性能测试最优
BatchSize(例如10000、50000),过大批次会占用过多内存,过小则增加数据库交互次数。 - 禁用非必要约束:若数据合法性已提前验证,可移除
SqlBulkCopyOptions.CheckConstraints、SqlBulkCopyOptions.FireTriggers等选项,减少数据库端校验开销。 - 预配置DataTable列:确保DataTable列类型与目标SQL表完全匹配,避免自动类型转换带来的性能损耗。
内容的提问来源于stack exchange,提问作者LSM07
相关产品推荐
相关产品推荐

