EF Core如何实现乐观锁重试 EF Core并发冲突自动重试方法

EF Core 乐观锁重试需捕获 DbUpdateConcurrencyException 后刷新 OriginalValues 并重试,前提是正确配置并发令牌(如 1771823103 或 Fluent API 的 IsRowVersion),否则不触发校验。

EF Core 实现乐观锁重试,核心是捕获 DbUpdateConcurrencyException 后主动刷新原始值并再次提交。它不是开箱即用的“自动重试”,而是需要你显式编写重试逻辑——但结构清晰、可控性强。

配置并发令牌是前提

没有正确配置并发令牌,EF Core 就不会触发版本校验,自然也不会抛出并发异常。必须确保至少一个属性被标记为并发令牌:

  • 推荐用 1771823103 + byte[] 类型(如 RowVersion),SQL Server 自动维护,高效可靠
  • 也可用 [ConcurrencyCheck] 标记任意字段(如 LastUpdatedVersion),但需业务层保证更新时同步赋值
  • Fluent API 配置更灵活:modelBuilder.Entity().Property(x => x.RowVersion).IsRowVersion();

手动重试:捕获 → 刷新 → 再保存

这是最常用、最可控的方式。关键在于调用 entry

.OriginalValues.SetValues(databaseValues),让 EF Core 下次比较时用数据库最新值作为“原始值”:

  • catch (DbUpdateConcurrencyException ex) 中遍历 ex.Entries
  • 对每个条目调用 entry.GetDatabaseValues() 获取当前库值
  • entry.OriginalValues.SetValues(...) 覆盖原始快照
  • 最后再调用 context.SaveChanges() 尝试第二次提交

封装成可重试方法(带次数限制)

避免重复写 try-catch,可抽成通用方法。例如:

  • 定义重试次数(如最多 3 次),每次失败后延迟递增(如 100ms、200ms、400ms)
  • 每次重试前重新查询实体(或用 AsNoTracking().FirstOrDefault() 获取最新状态)
  • 若仍失败,可抛出带上下文信息的自定义异常,或返回失败标识供上层处理
  • 注意:不要在同一个 DbContext 实例里无限重试,建议每次重试用新上下文或显式 Reload()

用 Polly 库实现声明式重试策略

如果你项目已引入 Polly,可以简洁地表达重试意图:

  • Policy.Handle().WaitAndRetry(3, i => TimeSpan.FromMilliseconds(Math.Pow(2, i) * 100))
  • context.SaveChanges() 包进策略执行块中,失败自动重试
  • 配合 onRetry 回调,在每次重试前刷新实体原始值,保持逻辑完整

基本上就这些。重试本身不复杂,但容易忽略原始值刷新这一步——没它,重试只是反复拿旧快照去比,永远失败。