You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何SqlBulkCopy.WriteToServerAsync不遵循await关键字?附问题排查

SqlBulkCopy.WriteToServerAsync 不遵循 await 导致 RowNotInTableException 的问题

这个问题的核心原因其实很隐蔽——你虽然在WriteToServerAsync前面加了await,但调用异步委托的地方根本没有等待异步操作完成,直接就清空了DataTable,导致后台还在运行的批量插入操作访问到了已经被移除的行。

问题根源分析

看你的ReadXMLInBatches方法,它是一个同步方法,但接收的processBatch是返回Task的异步委托。当批次数据满了之后,你执行了:

processBatch(table);
table.Rows.Clear();

这里的processBatch(table)只是发起了异步插入操作,并没有等待它完成就立刻清空了DataTable。而WriteToServerAsync内部是异步处理DataTable的行数据的,这时候后台线程还在读取DataTable的行,结果行被清空了,自然就抛出RowNotInTableException。

同时,这也解释了你看到的_isBulkCopyingInProgress为true的情况:前一批的异步插入还没结束,下一批又尝试调用WriteToServerAsync,但此时SqlBulkCopy还在处理上一批数据,所以这个字段还是true。

解决方案

要解决这个问题,需要把整个调用链改成异步的,确保每一批插入完成后再处理下一批:

  1. 修改ReadXMLInBatches为异步方法,并在调用processBatch时加上await:
public async Task ReadXMLInBatches(Func<DataTable, Task> processBatch) { 
    int batchSize = 10000; 
    var table = new DataTable(); 
    foreach (var col in columnNames) { 
        table.Columns.Add(col); 
    } 
    using (var reader = new StreamReader(pathToXml, Encoding.GetEncoding(encoding))) 
    using (var xmlReader = XmlReader.Create(reader)) { 
        string lastElement = null; 
        DataRow lastRow = null; 
        while (xmlReader.Read()) { 
            switch (xmlReader.NodeType) { 
                case XmlNodeType.Element: 
                    if (xmlReader.Name == "RECORD") { 
                        if (table.Rows.Count >= batchSize) { 
                            // 等待当前批次插入完成再清空表
                            await processBatch(table); 
                            table.Rows.Clear(); 
                        } 
                        lastRow = table.Rows.Add(); 
                    } 
                    lastElement = xmlReader.Name; 
                    break; 
                case XmlNodeType.Text: 
                    ReadMember(lastRow, lastElement, xmlReader.Value); 
                    break; 
            } 
        } 
        if (table.Rows.Count > 0) { 
            await processBatch(table); 
            table.Rows.Clear(); 
        } 
    } 
}
  1. 修改UpdateDBWithXML的参数和调用逻辑,确保等待readXmlInBatches完成:
public async Task UpdateDBWithXML(Func<Func<DataTable, Task>, Task> readXmlInBatches, string hashKey, string hash) { 
    using (var transaction = this.Context.Database.BeginTransaction(IsolationLevel.ReadUncommitted)) 
    using (var bulk = new SqlBulkCopy((SqlConnection)this.Connection, SqlBulkCopyOptions.Default, (SqlTransaction)transaction.UnderlyingTransaction)) { 
        bulk.DestinationTableName = "LegalContractorTemps"; 
        // 等待整个批量读取和插入流程完成
        await readXmlInBatches(async (DataTable table) => { 
            if (bulk.ColumnMappings.Count == 0) { 
                foreach (DataColumn column in table.Columns) { 
                    bulk.ColumnMappings.Add(new SqlBulkCopyColumnMapping(column.ColumnName, column.ColumnName)); 
                } 
            } 
            await bulk.WriteToServerAsync(table); 
        }); 
        await this.Context.Database.ExecuteSqlCommandAsync( 
            "EXECUTE dbo.LegalContractorsDataSynchronize @hashKey, @hash", 
            new SqlParameter("@hashKey", hashKey), 
            new SqlParameter("@hash", hash) 
        ); 
        transaction.Commit(); 
    } 
}

为什么这样能解决问题?

修改后,每一批数据的插入操作都会等待完成之后,才会执行table.Rows.Clear()和下一批数据的写入。这样SqlBulkCopy在处理数据时,DataTable的行始终是有效的,不会出现行被提前移除的情况,同时也能保证_isBulkCopyingInProgress字段在每一批插入完成后变回false,不会出现并发调用的问题。

内容的提问来源于stack exchange,提问作者hellouworld

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 17:17:40