EF Core并发控制

并发控制概念

  1. 并发控制:避免多个用户同时操作资源造成的并发冲突问题。
  2. 最好的解决方案:非数据库解决方案
  3. 数据库层面的两种策略:悲观、乐观

悲观锁

悲观并发控制一般采用行锁 ,表锁等排他锁对资源进行锁定,确保同时只有一个使用者操作被锁定的资源。

EF Core没有封装悲观并发控制的使用,需要开发人员编写原生SQL语句来使用悲观并发控制。不同数据库语法不一样。

MySQL方案:select * from T_Houses where Id = 1 for update

如果有其他查询操作也使用for update来查询Id=1的这条数据的话,那些查询就会被挂起,一直到针对这条数据的更新操作完成从而释放这个行锁,代码才会继续执行。

代码实现

根据数据库安装对应Nuget包,Mysql如下:

也可以使用官方的,没什么影响

Pemelo.EntityFrameworkCore.MySql

House类

class House
{
public long Id { get; set; }
public string Name {get;set;}
public string Owner {get;set;}
}

HouseConfig类

public class HouseConfig:IEntityTypeConfiguration<House>
{
public void Configure(EntityTypeBuilder<House> builder)
{
builder.ToTable("T_Houses");
builder.Property(b => b.Name).IsRequired();
}
}

DbContext类

public class MyDbContext:DbContext
{
public DbSet<House> Houses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
} protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
var connString = "server=localhost;user=root;password=root;database=ef1";
var serverVersion = new MySqlServerVersion(new Version(5, 7, 35));
optionsBuilder.UseMySql(connString, serverVersion);
}
}

迁移数据库

然后执行数据库迁移

安装Nuget:Microsoft.EntityFrameworkCore.Design,Microsoft.EntityFrameworkCore.Tools

  • Add-Migration Init
  • Update-database

随便给数据库添加几条信息

没有悲观版本

    public static void Main(string[] args)
{
Console.WriteLine("请输入您的名字");
string name = Console.ReadLine();
using (MyDbContext db = new MyDbContext())
{
var h = db.Houses.Single(h => h.Id == 1);
if (!string.IsNullOrEmpty(h.Owner))
{
if (h.Owner == name)
{
Console.WriteLine("房子已经被你抢到了");
}
else
{
Console.WriteLine($"房子已经被【{h.Owner}】占了");
}
return;
}
h.Owner = name;
Thread.Sleep(10000);
Console.WriteLine("恭喜你,抢到了");
db.SaveChanges();
Console.ReadLine();
}
}

可以看到实际上是jack抢到了,但是tom也打印了抢到!

有悲观锁的版本

锁和事务是相关的,因此通过BeginTransactionAsync()创建一个事务,并且在所有操作完成后调用CommitAsync()提交事务

Console.WriteLine("请输入您的名字");
string name = Console.ReadLine();
using MyDbContext db = new MyDbContext();
using (var tx = db.Database.BeginTransaction())
{
Console.WriteLine($"{DateTime.Now}准备select from update");
//加锁
var h = db.Houses.FromSqlInterpolated($"select * from T_houses where Id = 1 for update").Single();
Console.WriteLine($"{DateTime.Now}完成select from update");
if (!string.IsNullOrEmpty(h.Owner))
{
if (h.Owner == name)
{
Console.WriteLine("房子已经被你抢到了");
}
else
{
Console.WriteLine($"房子已经被【{h.Owner}】占了");
}
Console.ReadKey();
return;
}
h.Owner = name;
Thread.Sleep(5000);
Console.WriteLine("恭喜你,抢到了");
db.SaveChanges();
Console.WriteLine($"{DateTime.Now}保存完成");
//提交事务
tx.Commit();
Console.ReadKey();
}

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

问题

  1. 悲观并发控制的使用比较简单。
  2. 锁是独占、排他的,如果系统并发量很大的话,会严重影响性能,如果使用不当的话,甚至会导致死锁。
  3. 不同数据库的语法不一样。

乐观锁

原理

Update T_House set Owner = 新值 where Id = 1 and Owner = 旧值

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

EF Core配置

  1. 把被并发修改的属性使用IsConcurrencyToken()设置为并发令牌,

  2. public class HouseConfig:IEntityTypeConfiguration<House>
    {
    public void Configure(EntityTypeBuilder<House> builder)
    {
    builder.ToTable("T_Houses");
    builder.Property(b => b.Name).IsRequired();
    builder.Property(h => h.Owner).IsConcurrencyToken(); //这里设置列
    }
    }
  3. Console.WriteLine("请输入您的名字");
    string name = Console.ReadLine();
    using (MyDbContext db = new MyDbContext())
    {
    var h = db.Houses.Single(h => h.Id == 1);
    if (!string.IsNullOrEmpty(h.Owner))
    {
    if (h.Owner == name)
    {
    Console.WriteLine("房子已经被你抢到了");
    }
    else
    {
    Console.WriteLine($"房子已经被【{h.Owner}】占了");
    } Console.ReadKey();
    return;
    }
    h.Owner = name;
    Thread.Sleep(5000);
    try
    {
    db.SaveChanges();
    }
    catch (DbUpdateConcurrencyException ex)
    {
    Console.WriteLine("并发访问冲突");
    var entry1 = ex.Entries.First();
    string newValue = entry1.GetDatabaseValues().GetValue<string>("Owner");
    Console.WriteLine($"被{newValue}抢先了");
    }
    Console.ReadLine();
    }

效果截图

EF 生成的sql语句

多字段RowVersion

  1. SQLServer数据库可以用一个byte[]类型的属性做并发令牌属性,然后使用IsRowVersion()把这个属性设置为RowVersion类型,这样这个属性对应的数据库列就会被设置为ROWVERSION类型。对于这个类型的列,在每次插入或更新行时,数据库会自动为这一行的ROWVERSION类型的列其生成新值。
  2. 在SQLServer中,timestamp和rowversion是同一种类型的不同别名而已。

注意这里换成SQLServer数据库了!

实体类及配置

public class House
{
public long Id { get; set; }
public string Name { get; set; }
public string? Owner {get;set;}
public byte[]? RowVer{get;set;}
}
//builder.Property(h => h.Owner).IsConcurrencyToken(); //删除掉
builder.Property(h=>h.RowVer).IsRowVersion();

效果截图

概念

  1. 在MySQL(某些版本)等数据库中虽然也有类似的timestamp类型,但是由于timestamp类型的精度不够,并不适合在高并发的系统。
  2. 非SQLServer中,可以将并发令牌列的值更新为Guid的值
  3. 修改其他属性值的同时,使用h1.Rowver = Guid.NewGuid()手动更新并发令牌属性的值。

总结

  1. 乐观并发控制能够避免悲观锁带来的性能、死锁等问题,因此推荐使用乐观并发控制而不是悲观锁。
  2. 如果有一个确定的字段要被进行并发控制,那么使用IsConcurrencyToken()把这个字段设置为并发令牌即可。
  3. 如果无法确定一个唯一的并发令牌列,那么就可以引入一个额外的属性设置为并发令牌,并且在每次更新数据的时候,手动更新这一列的值。如果用的是SQLServer数据库,那么也可以采用RowVersion列,这样就不用开发者手动来在每次更新数据的时候,手动更新并发令牌的值了。

参考链接

每日一道面试题

  1. 什么是装箱和拆箱?

    答:从值类型接口转换到引用类型装箱。从引用类型转换到值类型拆箱。

  2. 抽象类和接口的相同点和不同点有哪些?何时必须声明一个类为抽象类?

    相同点:

    1. 都是用来实现抽象和多态的机制。
    2. 都不能被实例化,只能被继承或实现。
    3. 都可以包含抽象方法,即没有具体实现的方法。
    4. 都可以被子类继承或实现,并在子类中实现抽象方法。

    不同点:

    1. 抽象类可以包含非抽象方法,而接口只能包含抽象方法。
    2. 类只能继承一个抽象类,但可以实现多个接口。
    3. 抽象类的子类可以选择性地覆盖父类的方法,而接口的实现类必须实现接口中定义的所有方法。
    4. 抽象类可以有构造方法,而接口不能有构造方法。、

    一个类必须声明为抽象类的情况:

    1. 当类中存在一个或多个抽象方法时,类必须声明为抽象类。
    2. 当类需要被继承,但不能被实例化时,类必须声明为抽象类。
    3. 当类中的某些方法需要在子类中实现,而其他方法已经有了具体实现时,类可以声明为抽象类。

    总结:抽象类和接口都是实现抽象和多态的机制,但抽象类更适合用于一些具有公共实现的类,而接口更适合用于定义一组相关的方法,供多个类实现。抽象类可以包含非抽象方法和构造方法,而接口只能包含抽象方法。

EF Core并发控制的更多相关文章

  1. asp.net EF core 系列 作者:懒懒的程序员一枚

    asp.net core 系列 19 EFCore介绍写作逻辑一 .概述1.1 比较EF Core 和EF61.2 EF Core数据库提供程序 1.3 引用程序添加数据库提供程序1.4 获取Enti ...

  2. EF Core 数据库迁移(Migration)

    工具与环境介绍 1.开发环境为vs 2015 2.mysql EF Core支持采用  Pomelo.EntityFrameworkCore.MySql   源代码地址(https://github. ...

  3. Asp.net Core 通过 Ef Core 访问、管理Mysql

    本文地址:http://www.cnblogs.com/likeli/p/5910524.html 环境 dotnet Core版本:1.0.0-preview2-003131 本文分为Window环 ...

  4. EF Core 杂记

    本系列文章,将介绍本人在学习和使用EF Core的过程中的收获与心得. 或许有的地方讲的错误 欢迎大家批评指出. 1.EF Core 数据库迁移(Migration)

  5. MySQL官方.NET Core驱动已出,支持EF Core

    千呼万唤始出来MySQL官方.NET Core驱动已出,支持EF Core. 昨天MySQL官方已经发布了.NET Core 驱动,目前还是预览版,不过功能已经可用. NuGet 地址:https:/ ...

  6. EF Core 1.0 和 SQLServer 2008 分页的问题

    EF Core 1.0 在sqlserver2008分页的时候需要指定用数字分页. EF Core1.0 生成的分页语句中使用了 Featch Next.这个语句只有在SqlServer2012的时候 ...

  7. EntityFramework Core技术线路(EF7已经更名为EF Core,并于2016年6月底发布)

    官方文档英文地址:https://github.com/aspnet/EntityFramework/wiki/Roadmap 历经延期和更名,新版本的实体框架终于要和大家见面了,虽然还有点害羞.请大 ...

  8. EF Core CodeFirst实践 ( 使用MS SqlServer)

    这里使用 MS SQLSERVER ,网上大多使用 SQLite 先来一个CodeFirst 新建项目 这里我们选择  ASP.NET Core Web Application (.NET Core) ...

  9. ASP.NET Core 开发-Entity Framework (EF) Core 1.0 Database First

    ASP.NET Core 开发-Entity Framework Core 1.0 Database First,ASP.NET Core 1.0 EF Core操作数据库. Entity Frame ...

  10. ASP.NET Core 开发 - Entity Framework (EF) Core

    EF Core 1.0 Database First http://www.cnblogs.com/linezero/p/EFCoreDBFirst.html ASP.NET Core 开发 - En ...

随机推荐

  1. 小程序中使用 lottie 动画 | 踩坑经验分享

    最近被拉去支援紧急需求(赶在五一节假日前上线的,双休需要加班),参与到项目中才知道,开发的项目是微信小程序技术栈的.由于是临时支援,笔者也很久没开发过微信小程序了,所以挑选了相对独立,业务属性相对轻薄 ...

  2. VUE中具名插槽和匿名插槽的使用

    在我的项目中由于使用的是vue+element一个自用框架进行开发,插槽用法相较简单 比如在列表字段columns使用slotname即可 <template v-slot:_spec=&quo ...

  3. elasticsearch 6.2.4和elasticsearch-head环境搭建 使用docker-compose方式

    elasticsearch 6.2.4和elasticsearch-head测试环境搭建 使用docker-compose方式 一 背景说明 对于新手来说搭建一个elasticsearch的测试环境稍 ...

  4. PCI-E与SATA SSD

    为什么要采用PCI-E通道 目前在固态硬盘SSD中,有一部分采用了SATA3.0接口,而一些高端的固态硬盘则采用了PCI-E接口.那么为什么高端固态硬盘要采用PCI-E接口呢?为了弄清楚这个问题,先看 ...

  5. Splashtop调查显示:居家办公生产效率更高

    抱歉,本文又是个吃瓜新闻.不得不发,你懂得~ 端午节要到了,应该请大家赛龙舟,吃粽子来着. 研究表明,即使文字顺序打乱,读者都还是能毫无障碍地读懂一篇文章.或许,大家只是一目十行的看一下主要关键词就可 ...

  6. MySQL中drop/truncate/delete的区别

    1.Delete语句执行删除的过程是每次从表中删除一行,并且同时将删除操作作为事务记录在日志中保存以便进行进行回滚操作(只删除表数据). delete是DML,执行delete操作时,每次从表中删除一 ...

  7. Android 13 - Media框架 - 异步消息机制

    关注公众号免费阅读全文,进入音视频开发技术分享群! b7693967-317e-4c46-96d3-d40d9d87e382 由于网上已经有许多优秀的博文讲解了Android的异步消息机制(ALoop ...

  8. GNU gprof分析C性能

    参考 gprof的简单使用-anthony1983-ChinaUnix博客 Top (GNU gprof) (sourceware.org) c - Enable and disable gprof ...

  9. codemirror-editor-vue3 输入框信息太多 输入框宽度溢出隐藏

    我们把div注释看下之前溢出的效果 因为有form表单在里面任何标签上面设置都是不行 因为有校验要显示校验的信息overflow是不起作用的 要是单独的codemirror-editor-vue3 编 ...

  10. js整数类型

    <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8 ...