EF Core 中的并发处理

概念

并发问题是一个常见的问题,很多人认为并发只会发生在数据库操作上,其实不然,它一样可以发生在文件的读写操作上。只要我们从服务器访问资源并更新,就有可能会发生并发。但我们在开发中更多的是连接数据库来操作数据,所以我们习惯性的认为并发是数据库并发。

并发是指两个或多个用户试图同时修改同一资源造成的冲突。比如,A君读取数据以更新数据,但B君在A君更新数据之前先更新数据,A君更新操会导致三个值之间发生冲突。这三个值分别:

  • A君读取到的值(原始值)

  • B君更新的值(数据库值)

  • A君正在更新的值(当前值)

这种并发冲突如果不做任何处理会造成数据前后的不一致和完整性。比如:下单和退货发生并发冲突时将造成库存不一致,后果可想而知。

我们通过并发检测和解决并发冲突称为并发处理。

并发处理是一种机制,使众多用户能够同时访问相同的资源,同时确保资源在未来所有请求中保持数据的安全性、完整性和一致。

处理并发冲突有两种解决方法:乐观并发和悲观并发策略。

悲观并发

悲观并发每次访问资源时都上锁,使得其它用户无法访问该资源。这种策略就是假设用户每次访问资源都认定会更新资源,所以每次访问资源就上锁,当资源被修改时,同一资源上的所有其它并发操作都将被暂停,直到当前操作完成,并且该资源上的锁被释放。

这种方法在资源争用较高的场景中是一个不错的选择。可以在锁定持续时间较短的场景中利用悲观并发。但是,当用户与数据交互时,悲观并发不能很好的扩展,会导致资源锁住很长的时间。再者当互联网连接较弱或断开连接时,将无法正确管理数据,这会影响数据库的工作。

乐观并发

乐观并发假设用户每次访问资源都不会更新资源,所以资源不会被锁定。但是在更新的时候会判断在此期间其它用户是否有更新操作。

乐观并发遵循“保存最新数据”的策略。

乐观并发通常用于数据稀缺的场景,也就是常说的频繁读取,这样可以提高吞吐量,但也会消耗额外的服务器资源 。

乐观并发提高了性能也更好的支持扩展,因为它允许服务器在更短时间内为更多的客户端提供服务。

今天,我们来讨论 EF Core 是怎么处理数据库并发。在EF Core 中是不支持悲观并发,但有两种方式可用于乐观并发来进行并发冲突检测。

一种是将实体配置为并发令牌;另一种是在实体类中添加行版本属性。

使用并发令牌

Data Annotation 配置方式

public class Users
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public string PasswordHash { get; set; }
    [ConcurrencyCheck]
    public string ConcurrencyStamp { get; set; }
}

Fluent API 配置方式

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Users>()
        .Property(p => p.ConcurrencyStamp)
        .IsConcurrencyToken();
}

两种配置方式选择一种即可,使用并发令牌 EF Core 不会自动生成值,我们在更新实体记得给 ConcurrencyStamp 赋值。

使用版本属性

Data Annotation 配置方式

public class Roles
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    [Timestamp]
    public byte[] Timestamp { get; set; }
}

Fluent API 配置方式

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Roles>()
        .Property(p => p.Timestamp)
        .IsRowVersion();
}

也是两种选择一种,但与并发令牌不同的是不用给 Timestamp 赋值,EF Core 会自动产生一个值。

配置完之后 ,实体执行更新或删除操作时,EF Core 都会将请求中的信息值与数据库表中的值进行比较。

  • 如果两个值匹配,则执行操作。

  • 如果值不匹配,则更新或删除操作将中止并且抛出一个 DbUpdateConcurrencyException。

当 EF Core 抛出 DbUpdateConcurrencyException ,我们可以做如下选择进行处理:

  • 中止操作并要求用户刷新界面重新操作,称为使用他的,但会覆盖本地的操作数据。

  • 使用当前值(最新一次提交的数据)并尝试重新 SaveChanges ,称为使用我的,但会覆盖服务器上的数据。

  • 将冲突记录下来,让用户自己选择使用那个数据,称为手动解决。这种方式始终保持数据最新。

以下是个简单的冲突处理代码:

using (var context = new ApplicationDbContext())
{
    var role = context.Roles.Single(p => p.Id == 1);
    role.Name = "administrators";

    context.Database.ExecuteSqlRaw(
        "UPDATE dbo.Roles SET Description = '管理员组' WHERE Id = 1");

    var savedData = false;

    while (!savedData)
    {
        try
        {
            // 更新到数据库
            context.SaveChanges();
            savedData = true;
        }
        catch (DbUpdateConcurrencyException ex)
        {
            foreach (var item in ex.Entries)
            {
                if (item.Entity is Roles)
                {
                    var currentValues = entry.CurrentValues;
                    var dbValues = entry.GetDatabaseValues();

                    foreach (var prop in currentValues.Properties)
                    {
                        var currentValue = currentValues[prop];
                        var dbValue = dbValues[prop];
                    }

                    // 刷新原始值以绕过下一次并发检查
                    item.OriginalValues.SetValues(dbValues);
                }
                else
                {
                    throw new NotSupportedException( "未处理的并发冲突:" + item.Metadata.Name);
                }
            }
        }
    }
}

这个示例只是说明下 DbUpdateConcurrencyException 里的 Entries 及三种值。

EF Core 并发处理就到这。

祝大家学习愉快!

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值