发布于2026-07-08 阅读(0)
扫一扫,手机访问
先直接说一个核心观点:DbUpdateConcurrencyException 这个异常,与其说它是“报错”,不如把它理解为 EF Core 跟你打了个招呼,提醒你“有人动过你的数据了,你自己决定接下来怎么办”。如果你不捕获它、不配置并发令牌,那所谓的“乐观锁”策略其实根本没有生效。

可以把这个异常看作一次“介于成功与失败之间的汇报”。它只会在两个条件同时满足时才会被抛出:数据库中那一行的 RowVersion 值,与你加载时获取的值对不上号;并且,这个字段在 EF Core 里被正确标记成了并发令牌。很多开发者遇到这个异常就慌了神,但往往问题出在以下几个细节上:
[Timestamp] 属性,但字段类型写错了——必须是 byte[],写成 string 或 int 都没用。.HasDefaultValueSql("..."),却漏掉了最关键的 .IsRowVersion()。RowVersion 字段是 NULL,SQL Server 根本不会自动生成值。RowVersion 赋了值(比如 entity.RowVersion = new byte[8]),等于自己破坏了数据库的自动维护机制。ex.Entries 是唯一的入口,每个 EntityEntry 会暴露三套状态,这三套数据加起来,就是你做决策的全部依据:
FindAsync 或 FirstOrDefault 时从数据库读出来的旧数据,包含了旧的 RowVersion。RowVersion —— 那是 EF Core 自动生成的活儿。await entry.GetDatabaseValuesAsync()。这里必须提醒一个很容易忽略的点:DatabaseValues 可能返回 null —— 说明这条记录已经被删了。所以别急着调 .ToObject(),先判空。另外,千万别图省事在同步方法里用 .GetDatabaseValues(),那个重载已经被标记为过时,而且会卡死。
经典的处理策略是“拉最新数据 → 合并修改 → 重试提交”,但在实际代码中,经常看到开发者掉进这些坑里:
OriginalValues:只调了 SetValues(databaseValues) 还远远不够,必须显式调用 entry.OriginalValues.SetValues(databaseValues),否则下一次提交时,EF Core 拿的还是一个过期的 RowVersion 去比对,结果可想而知。Name,但 DatabaseValues 里的 Price 在过程中被别人动过,你用 SetValues 一股脑全设回去,等于把别人的修改给抹平了。正确的做法是只合并用户本次操作的字段。Product 关联多个 OrderItem。主表冲突后,子表的 OriginalValues 没跟着同步刷新,二次提交仍然失败。这才是真正的“连环坑”。需要明确一个事实:[Timestamp] 是 SQL Server 特有的语义。换到 PostgreSQL,你得用 bytea 类型搭配 .IsRowVersion();而到了 SQLite,它根本不支持自动 rowversion,只能退而求其次,改用 [ConcurrencyCheck] 手动维护一个 int 版本号。如果你的项目要跨多数据库,那最好在 OnModelCreating 里统一用 Fluent API 来配置,别依赖特性标注——否则 [Timestamp] 在 PostgreSQL 的迁移过程中会静默失效,而你还浑然不觉。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8