ToB企服应用市场:ToB评测及商务社交产业平台

标题: EF Core并发控制 [打印本页]

作者: 张国伟    时间: 2023-9-4 03:02
标题: EF Core并发控制
EF Core并发控制

并发控制概念

悲观锁

悲观并发控制一般采用行锁 ,表锁等排他锁对资源进行锁定,确保同时只有一个使用者操作被锁定的资源。
EF Core没有封装悲观并发控制的使用,需要开发人员编写原生SQL语句来使用悲观并发控制。不同数据库语法不一样。
MySQL方案:select * from T_Houses where Id = 1 for update
如果有其他查询操作也使用for update来查询Id=1的这条数据的话,那些查询就会被挂起,一直到针对这条数据的更新操作完成从而释放这个行锁,代码才会继续执行。
代码实现

根据数据库安装对应Nuget包,Mysql如下:
也可以使用官方的,没什么影响
  1. Pemelo.EntityFrameworkCore.MySql
复制代码
House类
  1. class House
  2. {
  3.         public long Id { get; set; }
  4.         public string Name {get;set;}       
  5.         public string Owner {get;set;}
  6. }
复制代码
HouseConfig类
  1. public class HouseConfig:IEntityTypeConfiguration<House>
  2. {
  3.     public void Configure(EntityTypeBuilder<House> builder)
  4.     {
  5.         builder.ToTable("T_Houses");
  6.         builder.Property(b => b.Name).IsRequired();
  7.     }
  8. }
复制代码
DbContext类
  1. public class MyDbContext:DbContext
  2. {
  3.      public DbSet<House> Houses { get; set; }
  4.     protected override void OnModelCreating(ModelBuilder modelBuilder)
  5.     {
  6.         base.OnModelCreating(modelBuilder);
  7.         modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
  8.     }
  9.     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  10.     {
  11.         base.OnConfiguring(optionsBuilder);
  12.         var connString = "server=localhost;user=root;password=root;database=ef1";
  13.         var serverVersion = new MySqlServerVersion(new Version(5, 7, 35));
  14.         optionsBuilder.UseMySql(connString, serverVersion);
  15.     }
  16. }
复制代码
迁移数据库

然后执行数据库迁移
安装Nuget:Microsoft.EntityFrameworkCore.Design,Microsoft.EntityFrameworkCore.Tools
随便给数据库添加几条信息
没有悲观版本
  1.     public static void Main(string[] args)
  2.     {
  3.         Console.WriteLine("请输入您的名字");
  4.         string name = Console.ReadLine();
  5.         using (MyDbContext db = new MyDbContext())
  6.         {
  7.             var h = db.Houses.Single(h => h.Id == 1);
  8.             if (!string.IsNullOrEmpty(h.Owner))
  9.             {
  10.                 if (h.Owner == name)
  11.                 {
  12.                     Console.WriteLine("房子已经被你抢到了");
  13.                 }
  14.                 else
  15.                 {
  16.                     Console.WriteLine($"房子已经被【{h.Owner}】占了");
  17.                 }
  18.                 return;
  19.             }
  20.             h.Owner = name;
  21.             Thread.Sleep(10000);
  22.             Console.WriteLine("恭喜你,抢到了");
  23.             db.SaveChanges();
  24.             Console.ReadLine();
  25.         }
  26.     }
复制代码


可以看到实际上是jack抢到了,但是tom也打印了抢到!
有悲观锁的版本

锁和事务是相关的,因此通过BeginTransactionAsync()创建一个事务,并且在所有操作完成后调用CommitAsync()提交事务
  1. Console.WriteLine("请输入您的名字");
  2. string name = Console.ReadLine();
  3. using MyDbContext db = new MyDbContext();
  4. using (var tx = db.Database.BeginTransaction())
  5. {
  6.     Console.WriteLine($"{DateTime.Now}准备select from update");
  7.     //加锁
  8.     var h = db.Houses.FromSqlInterpolated($"select * from T_houses where Id = 1 for update").Single();
  9.     Console.WriteLine($"{DateTime.Now}完成select from update");
  10.     if (!string.IsNullOrEmpty(h.Owner))
  11.     {
  12.         if (h.Owner == name)
  13.         {
  14.             Console.WriteLine("房子已经被你抢到了");
  15.         }
  16.         else
  17.         {
  18.             Console.WriteLine($"房子已经被【{h.Owner}】占了");
  19.         }
  20.         Console.ReadKey();
  21.         return;
  22.     }
  23.     h.Owner = name;
  24.     Thread.Sleep(5000);
  25.     Console.WriteLine("恭喜你,抢到了");
  26.     db.SaveChanges();
  27.     Console.WriteLine($"{DateTime.Now}保存完成");
  28.     //提交事务
  29.     tx.Commit();
  30.     Console.ReadKey();
  31. }
复制代码

可以看到tom 在27:58秒的时候完成了锁,所以程序提交的时候是tom抢到了,而不是jack,当执行SaveChanges()之前,行的锁会一直存在,直到SaveChanges()运行完成才会释放锁,这时jack才会完成锁。

问题

乐观锁

原理

Update T_House set Owner = 新值 where Id = 1 and Owner = 旧值
当Update的时候,如果数据库中的Owner值已经被其他操作更新为其他值了,那么where语句的值就会为false,因此这个Update语句影响的行数就是0,EF Core就知道发生并发冲突了,因此SaveChanges()方法就会抛出DbUpdateConcurrencyException异常。
EF Core配置

效果截图

EF 生成的sql语句


多字段RowVersion

注意这里换成SQLServer数据库了!
实体类及配置
  1. public class House
  2. {
  3.     public long Id { get; set; }
  4.     public string Name { get; set; }
  5.     public string? Owner {get;set;}
  6.     public byte[]? RowVer{get;set;}
  7. }
复制代码
  1. //builder.Property(h => h.Owner).IsConcurrencyToken(); //删除掉
  2. builder.Property(h=>h.RowVer).IsRowVersion();
复制代码
效果截图



概念

总结

参考链接

每日一道面试题


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4