EF Core批量SaveChanges遇并发异常时其余实例的更新结果
我有一个基于EF Core + SQL的应用,其中名为Indicator的模型将TimeStampUpdated属性设置为ConcurrencyToken。
在一个长期运行的任务中,我通过以下代码读取所有需要标记为重复的指标:
var indicatorsToRepeat = await careOrganisationMessagingDbContext.Indicators .Where(ind => ind.StateRepeat == true && ind.TimestampRepeat != null && EF.Functions.DateDiffSecond(ind.TimestampRepeat, timeStampNow) >= 0) .ToListAsync(); if (indicatorsToRepeat != null && indicatorsToRepeat.Any()) { foreach (Indicator indicator in indicatorsToRepeat) { indicator.MustRepeatNow = true; indicator.TimestampRepeat = DateTime.UtcNow.AddSeconds((int)indicator.RepeatInterval); } try { await careOrganisationMessagingDbContext.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { // Do nothing } }
该任务每2秒执行一次重复检测,且从不修改TimeStampUpdated。而“即发即弃”的任务会更新指标的State和TimeStampUpdated,可能导致indicatorsToRepeat列表中的一个或多个实例过期。
我要求更新操作优先,重复检测任务不能覆盖更新任务的修改。为此我将TimeStampUpdated设为ConcurrencyToken,当该值变化时更新会失败。
核心问题:若有10个指标需执行SaveChanges,其中第三个因并发(读取与保存间TimeStampUpdated变更)无法更新,其余第1、2、4至10个指标会成功更新吗?还是会在第三个异常后立即终止?
默认情况下,整个SaveChanges操作会回滚,所有10个指标的修改都不会生效。
原因是EF Core在调用SaveChangesAsync()时,会将所有待修改的操作包装在一个数据库事务中执行。只要其中任意一个实体的更新触发并发异常(DbUpdateConcurrencyException),整个事务就会被回滚,已经执行的修改(比如第1、2个指标的更新)也会被撤销,后续的指标(4-10)不会再尝试更新。
如果想要实现“部分成功”——即让未发生并发冲突的指标正常更新,冲突的指标跳过——你需要调整代码逻辑:
- 逐个处理每个指标,单独调用
SaveChangesAsync(),并为每个操作单独捕获并发异常; - 或者手动控制事务范围,将无冲突的变更分批提交,但这种方式需要额外的代码复杂度。
不过按照你当前的需求(更新操作优先,重复检测任务不能覆盖更新),当前的代码逻辑虽然会让所有修改回滚,但也避免了误覆盖已更新的指标。如果需要保留无冲突的修改,建议改成逐个处理的方式。
内容的提问来源于stack exchange,提问作者Edwin

