EF Core 乐观并发控制(并发令牌)
文章目录
- 前言
- 一、乐观并发的核心思想
- 二、实现方法
- 1)使用并发令牌(Concurrency Token)
- 2)处理并发冲突
- 三、工作原理
- 四、适用场景
- 五、与悲观并发的对比
- 六、最佳实践
- 总结
前言
Entity Framework (EF) Core 默认支持 乐观并发控制(Optimistic Concurrency Control),它通过检测数据冲突(而不是显式加锁)来保证数据一致性。
一、乐观并发的核心思想
- 无锁机制:允许多个事务同时读取和修改数据,提交时检查数据是否被其他事务修改。
- 冲突检测:通过版本号(RowVersion)或字段值比较,如果数据已被修改,则抛出
DbUpdateConcurrencyException。
二、实现方法
1)使用并发令牌(Concurrency Token)
-
为实体添加一个并发标记字段(如 RowVersion),每次更新时检查该字段是否与数据库中的值一致。
-
示例:通过 [ConcurrencyCheck] 特性标记字段
public class House{public long Id { get; set; }public string Name{ get; set; }[ConcurrencyCheck] // 标记为并发令牌public string Owner{ get; set; }}
-
示例:或通过 Fluent API 配置
protected override void OnModelCreating(ModelBuilder modelBuilder) {modelBuilder.Entity<House>().Property(p => p.Owner)//.IsRowVersion() // 自动映射为 SQL Server 的 `rowversion` 类型.IsConcurrencyToken(); // 标记为并发令牌 }
public class House {public long Id { get; set; }public string Name{ get; set; } public string Owner{ get; set; }public byte[] RowVersion { get; set; } } public void Configure(EntityTypeBuilder<House> builder) {builder.ToTable("T_Houses");builder.Property(h=>h.Name).IsRequired();//builder.Property(h=>h.Owner).IsConcurrencyToken();builder.Property(h=>h.RowVersion).IsConcurrencyToken().IsRowVersion(); }
2)处理并发冲突
-
当检测到数据已被修改时,EF Core 会抛出 DbUpdateConcurrencyException,开发者需捕获并处理冲突。
try {var house = await context.Houses.FindAsync(houseId);house.Owner = "Tom";await context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException ex) {// 处理冲突var entry = ex.Entries.Single();var databaseValues = await entry.GetDatabaseValuesAsync();if (databaseValues == null){// 数据已被删除Console.WriteLine("数据已被删除!");}else{// 获取当前数据库中的值var currentValues = databaseValues.ToObject() as House;// 策略1:使用数据库最新值覆盖当前修改entry.OriginalValues.SetValues(databaseValues);// 策略2:合并值(手动处理冲突)// currentValues 是数据库中的最新值// entry.Entity 是当前尝试提交的值// 例如:保留用户修改的某些字段,合并其他字段entry.Entity.Owner= "Tom";entry.Entity.RowVersion = currentValues.RowVersion;// 重新提交await context.SaveChangesAsync();} }
三、工作原理
-
查询数据:读取数据时,EF Core 会记录并发令牌的原始值(如 RowVersion)。
-
更新数据:提交修改时,生成的 SQL 会包含 WHERE 条件,检查并发令牌是否未被修改。
UPDATE [T_Houses] SET [Owner] = @p0 WHERE [Id] = @p1 AND [Owner] = @p2;
-
冲突检测:如果受影响的行数为 0(即 RowVersion 不匹配),抛出异常。
四、适用场景
- 低冲突概率:适合大部分时间数据竞争较少的场景。
- 高吞吐需求:避免锁机制的开销,提升性能。
- 分布式系统:无锁机制更适合跨服务的并发操作。
五、与悲观并发的对比
乐观并发 | 悲观并发 | |
---|---|---|
实现方式 | 版本号或字段检查(RowVersion、并发令牌) | 显式加锁(事务+锁机制) |
性能 | 低冲突时更高效 | 高竞争时可能更高效 |
复杂度 | EF Core 内置支持,自动检测冲突 | 需要手动管理锁和事务 |
数据竞争 | 可能需重试或合并数据 | 强制串行化,避免冲突 |
六、最佳实践
- 选择并发令牌
优先使用 RowVersion(自动递增的二进制字段),而非业务字段。
若使用业务字段(如 LastUpdatedTime),需确保其值在每次更新时被修改。 - 冲突处理策略
客户端优先:强制覆盖数据库的值(需谨慎)。
数据库优先:放弃当前修改,使用最新值。
合并值:手动合并冲突字段(如用户编辑的字段优先)。 - 重试机制
在分布式系统中,可为关键操作添加重试逻辑(如 Polly 库)。
总结
EF Core 的乐观并发通过版本号或字段值检测冲突,无需显式加锁,适合低竞争场景。通过 ConcurrencyCheck 或 IsRowVersion() 配置并发令牌,并在冲突时通过 DbUpdateConcurrencyException 实现灵活的数据合并或重试逻辑。
- 乐观并发控制能够避免悲观锁带来的性能、死锁等问题,因此推荐使用并发控制而不是悲观锁。
- 如果有一个确定的字段要被进行并发控制,那么使用IsConcurrencyToken()把这个字段设置为并发令牌即可;
- 如果无法确定一个唯一的并发令牌列,那么就可以引入一个额外的属性设置为并发令牌,并且在每次更新数据库的时候,手动更新这一列的值;如果用的是SQL Server数据库,那么也可以采用RowVersion列,设置为并发令牌列。
原文地址:https://blog.csdn.net/lei1083929965/article/details/146607245
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mrgr.cn/news/96316.html 如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mrgr.cn/news/96316.html 如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!