EF Core 实现乐观锁重试,核心是捕获
DbUpdateConcurrencyException后主动刷新原始值并再次提交。它不是开箱即用的“自动重试”,而是需要你显式编写重试逻辑——但结构清晰、可控性强。
配置并发令牌是前提
没有正确配置并发令牌,EF Core 就不会触发版本校验,自然也不会抛出并发异常。必须确保至少一个属性被标记为并发令牌:
推荐用1771666242+
byte[]类型(如
RowVersion),SQL Server 自动维护,高效可靠 也可用
[ConcurrencyCheck]标记任意字段(如
LastUpdated或
Version),但需业务层保证更新时同步赋值 Fluent API 配置更灵活:
modelBuilder.Entity<t>().Property(x => x.RowVersion).IsRowVersion();</t>
手动重试:捕获 → 刷新 → 再保存
这是最常用、最可控的方式。关键在于调用
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<dbupdateconcurrencyexception>().WaitAndRetry(3, i => TimeSpan.FromMilliseconds(Math.Pow(2, i) * 100))</dbupdateconcurrencyexception>把
context.SaveChanges()包进策略执行块中,失败自动重试 配合
onRetry回调,在每次重试前刷新实体原始值,保持逻辑完整
基本上就这些。重试本身不复杂,但容易忽略原始值刷新这一步——没它,重试只是反复拿旧快照去比,永远失败。
