DbUpdateConcurrencyException 是 EF Core 乐观并发冲突的唯一信号,需显式配置并发令牌(如[Timestamp]或[ConcurrencyCheck])才会触发;未配置时静默覆盖最危险;Reload 非万能,会丢弃用户输入且不递归刷新关联实体;客户端胜出需手动同步 OriginalValue 或绕过检查;独立关联会导致并发失效,必须显式声明外键属性。

DbUpdateConcurrencyException 是 EF Core 乐观并发冲突的唯一信号,遇到它不等于失败,而是进入“协商阶段”的开始。
为什么 SaveChanges 会抛出 DbUpdateConcurrencyException
EF Core 默认不启用乐观并发,必须显式配置并发令牌(如 [Timestamp] 或 [ConcurrencyCheck])。没配就直接改数据再保存,即使别人已改过,也不会报这个异常——这恰恰是最危险的情况。
-
[Timestamp]只支持byte[]类型,SQL Server 会自动维护rowversion列,推荐用于主实体 -
[ConcurrencyCheck]可用于任意可读写属性(如UpdatedAt、Version),但需你手动更新值,容易漏写 - 多个属性可同时标记为并发令牌,WHERE 条件会全部叠加,提升检测精度但也增加冲突概率
Reload 不是万能解药:数据库胜出的代价
Reload() 看似简单,但它把用户刚输入的新值整个丢弃,用数据库当前值覆盖——这在编辑表单场景下等于让用户重填一遍。实际项目中常被误用为“兜底方案”,结果引发用户投诉。
- 只对单实体有效:
ex.Entries.Single().Reload()假设只有一个冲突项;若批量更新失败,Entries可能含多个,需遍历处理 - Reload 后实体状态变为
Unchanged,如果还想保存用户修改,得重新赋值再调SaveChanges - 若关联实体也参与更新(如 Blog → Posts),
Reload()不递归刷新导航属性,可能留下脏状态
客户端胜出时必须绕过 WHERE 并强制更新
当确定要以用户输入为准(比如客服后台强制修正错误),不能靠 Reload,而要跳过并发检查逻辑:
- 先调
context.Entry(entity).Property(e => e.Version).OriginalValue = entity.Version,把原始值改成当前值,让 WHERE 条件恒成立 - 或更直接:移除并发令牌映射(仅限临时操作),用
context.Database.ExecuteSqlRaw手写 UPDATE - 注意:强制覆盖会丢失他人修改,务必加业务日志(如记录谁、何时、覆盖了哪些字段)
独立关联会让并发处理彻底失效
如果你的实体用的是独立关联(即外键没声明为属性,靠 EF 内部管理),DbUpdateConcurrencyException 的 Entries 可能无法准确定位冲突来源,甚至不触发异常。这是 EF6/EF Core 共有的深层限制,不是 bug。
- 必须把外键显式声明为属性,例如
public int? AuthorId { get; set; },并用HasForeignKey显式配置 - 否则即使加了
[Timestamp],EF 也可能在生成 SQL 时忽略并发条件,导致“静默覆盖” - 这种设计缺陷在 EF Core 8 中仍未修复,生产环境务必规避
Reload 和重试循环,最后发现用户改了半天还是被刷掉。


















