EF Core 默认采用乐观并发控制(Optimistic Concurrency Control),不锁数据,而是在
SaveChanges或
SaveChangesAsync执行更新/删除时,检查关键字段是否被其他操作改过。一旦发现不一致,就抛出
DbUpdateConcurrencyException,由你决定怎么处理——不是自动覆盖,而是交还控制权。
用时间戳(RowVersion)做最稳妥的并发令牌
这是推荐做法,尤其在 SQL Server 中。数据库自动生成并维护
rowversion字段,每次更新整行都会变,完全无需业务代码干预。 实体类中加
1771665942特性,类型为
byte[]:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
1771665942
public byte[] RowVersion { get; set; }
}
UPDATE Products SET Name = @p0 WHERE Id = @p1 AND RowVersion = @p2若 WHERE 不匹配(说明别人已更新),影响行数为 0,触发异常
用普通字段做并发令牌(需手动维护)
适合 MySQL、PostgreSQL 等不支持
rowversion的数据库,或业务上有意义的字段(如
LastModified、
Version)。 用 Fluent API 标记字段为并发令牌:
modelBuilder.Entity
.Property(p => p.Version)
.IsConcurrencyToken();
entity.Version++或
entity.LastModified = DateTime.UtcNow) 否则它不会变,起不到检测作用
捕获并处理 DbUpdateConcurrencyException
异常发生后,别直接报错,要读取当前数据库真实值,再决定合并策略。
遍历ex.Entries获取每个冲突实体 调用
entry.GetDatabaseValuesAsync()拿到最新数据库快照 对比
OriginalValues(你加载时的值)、
CurrentValues(你修改后的值)、
DatabaseValues(别人刚写入的值) 常见策略有:客户端优先(强制覆盖)、服务端优先(丢弃本次修改)、手动合并(比如只保留用户改的 Name,保留数据库的 Status) 调用
entry.OriginalValues.SetValues(databaseValues)同步原始值后,可重试
SaveChangesAsync
避免并发问题的补充建议
对高竞争字段(如库存、余额),考虑结合数据库原生操作(UPDATE ... SET Stock = Stock - 1 WHERE Stock >= 1)绕过 EF 的乐观锁逻辑 短事务 + 快速响应,减少“读-改-写”窗口期 前端提交前可加版本号校验(比如把
RowVersion作为隐藏字段传回),提前提示用户数据已变更 悲观锁(如
SELECT ... FOR UPDATE)在 EF Core 中需手动写原生 SQL 或使用事务隔离级别,不推荐作为默认方案
基本上就这些。核心就三点:选对令牌字段、理解 WHERE 条件怎么生成、异常来了别慌——读、比、定、重试。
