不管用——单独加[concurrencycheck]无效,它不生成并发令牌也不参与where校验;真正起作用的是[timestamp]或isconcurrencytoken()配置,且令牌值必须随每次更新自动变化。

EF Core 的 ConcurrencyCheck 属性到底管不管用?
不管用——至少单独加 [ConcurrencyCheck] 是无效的。这个特性本身不生成并发令牌(concurrency token),也不参与 WHERE 子句的乐观并发校验。它只是告诉 EF Core:“这个字段变化时,也参与变更跟踪比较”,但**不会触发并发异常**,也不会自动更新 WHERE 条件。真正起作用的是 [Timestamp] 或 [ConcurrencyToken] 配置。
必须用 [Timestamp] 或 IsConcurrencyToken() 才能抛出 DbUpdateConcurrencyException
只有被标记为并发令牌的属性,才会在 SaveChanges() 时被加入 SQL 的 WHERE 子句。如果数据库中该值已变,EF Core 就收不到任何行更新,从而抛出 DbUpdateConcurrencyException。
实操建议:
-
[Timestamp]只能用于byte[]类型,且一个实体最多一个;EF Core 会自动设为IsRowVersion = true - 更灵活的方式是用 Fluent API:
modelBuilder.Entity<blog>().Property(e => e.Version).IsConcurrencyToken()</blog>,支持int、long、DateTime等类型 - 若用
[ConcurrencyToken]特性(.NET 5+),需配合ValueGeneratedOnAddOrUpdate()或手动维护值,否则更新时令牌不变,校验失效
手动处理 DbUpdateConcurrencyException 时,别直接调 Reload()
捕获异常后常见错误是调 entry.Reload() 再重试——这会覆盖用户刚改的内存值。正确做法是:先获取数据库当前值,再决定如何合并。
典型处理步骤:
- 遍历
exception.Entries,对每个冲突实体调entry.GetDatabaseValues() - 对比
entry.CurrentValues(用户修改后)和databaseValues(库中当前) - 选择策略:用用户值覆盖(
entry.OriginalValues.SetValues(databaseValues))、保留用户修改(entry.OriginalValues.SetValues(entry.CurrentValues)),或提示用户解决冲突 - 最后调
SaveChanges()重试
并发令牌字段必须随每次更新自动变化,否则校验形同虚设
比如用 int Version 作令牌,但没在 SaveChanges 前自增,或没配 ValueGeneratedOnAddOrUpdate(),那每次 UPDATE 的 WHERE 条件都还是旧值,根本不会触发并发异常。
常见疏漏点:
- 用
[Timestamp]却忘了数据库列是rowversion类型(SQL Server)或bytea(PostgreSQL),导致迁移失败 - 用
DateTime作令牌,在高并发下可能因精度不足(如只到秒)导致多个请求拿到相同时间戳 - 在领域层手动赋值并发字段时,没同步更新
entry.OriginalValues,导致 EF Core 仍拿旧值拼 WHERE
真正关键的不是加什么特性,而是确保令牌值在每次成功更新后必然变化,并且 EF Core 能把它准确塞进 WHERE 条件里。漏掉任意一环,乐观并发就只是个摆设。











