慢吞云雾缓吐愁 发表于 2025-3-29 15:38:59

EF Core 乐观并发控制(并发令牌)

前言

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; }
       
          // 标记为并发令牌
          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
SET = @p0
WHERE = @p1 AND = @p2;

[*] 冲突检测:如果受影响的行数为 0(即 RowVersion 不匹配),抛出异常。
四、实用场景


[*]低冲突概率:得当大部门时间数据竞争较少的场景。
[*]高吞吐需求:避免锁机制的开销,提拔性能。
[*]分布式体系:无锁机制更得当跨服务的并发操作。
五、与悲观并发的对比

乐观并发悲观并发实现方式版本号或字段检查(RowVersion、并发令牌)显式加锁(事务+锁机制)性能低冲突时更高效高竞争时可能更高效复杂度EF Core 内置支持,自动检测冲突必要手动管理锁和事务数据竞争可能需重试或合并数据逼迫串行化,避免冲突 六、最佳实践


[*]选择并发令牌
优先使用 RowVersion(自动递增的二进制字段),而非业务字段。
若使用业务字段(如 LastUpdatedTime),需确保其值在每次更新时被修改。
[*]冲突处置惩罚策略
客户端优先:逼迫覆盖数据库的值(需谨慎)。
数据库优先:放弃当前修改,使用最新值。
合并值:手动合并冲突字段(如用户编辑的字段优先)。
[*]重试机制
在分布式体系中,可为关键操作添加重试逻辑(如 Polly 库)。
总结

EF Core 的乐观并发通过版本号或字段值检测冲突,无需显式加锁,得当低竞争场景。通过 ConcurrencyCheck 或 IsRowVersion() 配置并发令牌,并在冲突时通过 DbUpdateConcurrencyException 实现机动的数据合并或重试逻辑。

[*]乐观并发控制能够避免悲观锁带来的性能、死锁等题目,因此推荐使用并发控制而不是悲观锁。
[*]如果有一个确定的字段要被进行并发控制,那么使用IsConcurrencyToken()把这个字段设置为并发令牌即可;
[*]如果无法确定一个唯一的并发令牌列,那么就可以引入一个额外的属性设置为并发令牌,而且在每次更新数据库的时间,手动更新这一列的值;如果用的是SQL Server数据库,那么也可以接纳RowVersion列,设置为并发令牌列。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: EF Core 乐观并发控制(并发令牌)