SQL Server/Entity Framework触发重复异常后如何继续插入数据
问题根因
EF Core 的 DbContext 触发 SaveChanges 异常后,上下文不会自动重置状态,之前添加的、触发唯一索引冲突的实体仍然被 ChangeTracker 追踪。后续执行 SaveChanges 时,EF Core 仍会把所有被追踪的待提交实体一起发送到数据库,哪怕后续新增的是合法数据,之前残留的冲突实体还是会触发重复键异常,导致后续插入全部失败。
你的代码还有两个细节问题:
AddResultAsync方法中调用Context.Results.AddAsync(result)没有等待返回的ValueTask,且普通实体新增不需要调用异步Add方法,直接用同步Add即可- 无论插入成功还是失败,你都把
newResult1加入了返回结果列表results,最终返回的结果会包含插入失败的重复数据
修复方案
方案1:异常时分离冲突实体(改动最小)
在catch块中检测到唯一键冲突异常时,把当前触发错误的实体从ChangeTracker中移除,避免后续提交时带上该脏数据。修改后的循环逻辑如下:
for (var j = 0; j < resultDetails.Count(); j++) { var rd = resultDetails.ElementAt(j); var newResult1 = new Result { AthleteFEIID = rd.AthleteFEIID, CompetitionCode = competition.CompetitionCode, HorseId = horse.Id, }; try { _resultsService.Context.Results.Add(newResult1); await _resultsService.CompleteAsync(); // 插入成功再加入返回列表 results.Add(newResult1); } catch (DbUpdateException ex) when (ex.InnerException is SqlException sqlEx && (sqlEx.Number == 2601 || sqlEx.Number == 2627)) { // 2601、2627 是SQL Server唯一索引/唯一约束冲突的固定错误号 // 把冲突实体从上下文追踪中分离,清除脏状态 _resultsService.Context.Entry(newResult1).State = EntityState.Detached; // 此处可按需添加重复数据日志逻辑 } }
同时修正服务层的新增方法,去掉不必要的异步Add调用:
public Task AddResultAsync(Result result) { Context.Results.Add(result); return Task.CompletedTask; }
方案2:单条数据使用独立DbContext(隔离性最好)
如果使用依赖注入容器,可以在循环内部为每一条数据创建独立的DbContext作用域,单条数据插入失败直接销毁对应作用域,完全不会污染其他插入操作的上下文状态,适合对数据隔离要求高的场景。
性能优化建议
如果批量插入的数据量较大,循环内单次提交的性能很差,且依赖异常捕获做重复判断的效率很低。可以在插入前先根据唯一索引对应的三个字段,一次性查询数据库中已存在的记录,在内存中先过滤掉重复项,再批量提交剩余合法数据,能大幅减少数据库交互次数和异常触发开销。
内容的提问来源于stack exchange,提问作者Vlad Vorobyev
相关产品推荐
相关产品推荐

