Entity Framework Core无竞态整数字段自增更新的C#实现方法
与目标SQL等效且无竞态风险的EF Core实现方案
你需要实现的原子更新SQL逻辑如下:
UPDATE TheTable SET RevisionId = RevisionId + 1 WHERE notPk = @someValue
要完全规避竞态条件,核心要求是更新操作必须在数据库端原子执行,不能先把数据读到内存修改再写回,避免两次请求中间的数据状态被篡改。以下是不同EF Core版本下的实现方案:
EF Core 7.0及以上版本(推荐方案)
直接使用内置的ExecuteUpdateAsync方法,会自动生成和需求完全一致的原子更新SQL,无需手写原生语句:
// dbContext为你的上下文实例 var affectedRows = await dbContext.TheTable .Where(t => t.NotPk == someValue) .ExecuteUpdateAsync(setter => setter.SetProperty(t => t.RevisionId, t => t.RevisionId + 1) );
- 整个操作全程在数据库端原子执行,完全不存在竞态风险
- 返回值
affectedRows为匹配筛选条件被成功更新的行数 - 不需要额外调用
SaveChanges,方法执行后更新直接生效
EF Core 6.0及更低版本兼容方案
低版本没有内置批量更新方法,可直接执行参数化的原生SQL实现同等效果:
var affectedRows = await dbContext.Database.ExecuteSqlInterpolatedAsync( $"UPDATE TheTable SET RevisionId = RevisionId + 1 WHERE notPk = {someValue}" );
- EF Core会自动对插值参数做SQL注入防护,安全性有保障
- 同样是数据库端原子执行,无竞态风险
禁止使用的错误写法
不要使用「先查询实体→修改属性→调用SaveChanges」的逻辑,如下代码存在竞态风险:
// 错误示例,存在竞态 var entity = await dbContext.TheTable.FirstAsync(t => t.NotPk == someValue); entity.RevisionId += 1; await dbContext.SaveChangesAsync();
这种写法在查询和保存的间隔中如果有其他请求修改了同一条数据的RevisionId,会导致覆盖更新,必须额外加并发令牌控制才能解决,性能和代码复杂度远高于原子更新方案。
内容的提问来源于stack exchange,提问作者Fritz
相关产品推荐
相关产品推荐

