dbupdateconcurrencyexception需在savechanges()外层try-catch捕获,其entries提供original、current和database三套值用于冲突解决;必须配置[timestamp]或isrowversion()启用并发检查,且rowversion字段不可手动赋值。

EFCore 并发冲突报 DbUpdateConcurrencyException 怎么捕获和处理
遇到并发修改同一行数据时,EF Core 默认会抛出 DbUpdateConcurrencyException,而不是静默覆盖。这不是 bug,是设计使然——它逼你显式决定怎么处理冲突。
关键不是“怎么避免异常”,而是“怎么在异常发生后做合理决策”。常见做法是在 SaveChanges() 或 SaveChangesAsync() 外层 try-catch:
try {
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex) {
// 处理逻辑见下个副标题
}
注意:这个异常只在启用并发令牌(如 RowVersion)且数据库实际检测到版本不匹配时才抛出;没配令牌的话,EF Core 根本不检查,并发覆盖就静默发生了。
为什么必须用 [Timestamp] 或 IsRowVersion() 才能触发并发检查
RowVersion 不是“自动生效”的功能,它依赖两个条件同时满足:数据库字段类型是 rowversion(SQL Server)或 byte[] + 数据库对应二进制类型(如 PostgreSQL 的 bytea),以及 EF Core 明确标记该属性为并发令牌。
- SQL Server 下,实体类里加
[Timestamp]特性,字段类型必须是byte[] - Fluent API 中更推荐用
entity.Property(e => e.RowVersion).IsRowVersion(),兼容性更好,且能绕过[Timestamp]在某些 ORM 混用场景下的歧义 - 别用
DateTime或int模拟时间戳——EF Core 不识别它们为并发令牌,也不会在 WHERE 条件中带上旧值比对
漏掉任一环节,DbUpdateConcurrencyException 就永远不会出现,你以为的“乐观锁”其实根本没起作用。
DbUpdateConcurrencyException.Entries 里能拿到什么,怎么用
这个异常对象的 Entries 属性是核心突破口,它返回所有因并发失败而卡住的 EntityEntry。每个 entry 包含三套值:OriginalValues(加载时的原始 DB 值)、CurrentValues(你刚改完、准备提交的值)、DatabaseValues(此刻数据库里真实最新的值)。
典型修复策略是“重读再合并”,例如:
foreach (var entry in ex.Entries) {
var databaseValues = await entry.GetDatabaseValuesAsync();
if (databaseValues == null) {
throw new InvalidOperationException("数据库中已删除该记录");
}
// 把当前修改合并进最新数据库值,或提示用户手动解决
entry.OriginalValues.SetValues(databaseValues);
}
注意:GetDatabaseValuesAsync() 是异步的,别在同步上下文中硬调 .Result;如果业务允许丢弃用户修改,直接 entry.OriginalValues.SetValues(databaseValues) 后重试 SaveChanges 即可。
SQL Server 的 rowversion 字段为什么不能手动赋值
SQL Server 的 rowversion 是数据库自动生成的、只读的二进制计数器,每次更新行就自动变。EF Core 依赖它做 WHERE 比对:WHERE Id = @id AND RowVersion = @originalRowVersion。一旦你在代码里给 RowVersion 属性赋了任意值(比如 new byte[8]),EF Core 就会把这个“假版本”塞进 WHERE 条件,导致永远不匹配、必抛异常。
- 实体构造函数、初始化器、AutoMapper 映射里都别碰
RowVersion字段 - DTO 转实体时,确保
RowVersion不被反序列化覆盖(例如用[JsonIgnore]或 AutoMapper 的ForMember(..., opt => opt.Ignore())) - 前端传参时,如果带了
rowVersion字段,后端绑定模型要明确排除,否则 ASP.NET Core Model Binding 会悄悄把它设进去
最稳妥的做法:把 RowVersion 属性设为 private set,只允许 EF Core 内部写入。











