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. Pinpoint对k8s关键业务模块进行全链路监控(17)

    一.全链路监控概述 1.1 什么是全链路监控 在分布式微服务架构中,系统为了接收并处理一个前端用户请求,需要让多个微服务应用协同工作,其中 的每一个微服务应用都可以用不同的编程语言构建,由不同的团队开 ...

  2. 关于Web的欢迎页面的开发设置

    关于Web的欢迎页面的开发设置 每博一文案 命运总是不如人愿.但往往是在无数的痛苦中,在重重的矛盾和艰辛中,才是人成熟起来. 一次邂逅,一次目光的交融,就是永远的合二为一,就是与上帝的契约:总是风暴雷 ...

  3. nmcli device status状态为unmanaged

    遇到报错:(device lo not available because device is strictly unmanaged) nmcli device status 查看设备状态 nmcli ...

  4. fastposter v2.7.1 紧急发布 电商海报编辑器

    fastposter v2.7.1 紧急发布 电商海报编辑器 fastposter海报生成器,电商海报编辑器,电商海报设计器,fast快速生成海报 海报制作 海报开发.二维码海报,图片海报,分享海报, ...

  5. Splashtop 教育行业用户增加700%

    ​ 由于新冠肺炎大流行继续限制对大学.学院和K-12学校的计算机实验室的物理访问,Splashtop的销售数据表明,越来越多的学校开始使用远程访问软件作为使用计算机实验室资源的替代方法. 在6月到8月 ...

  6. python计算机视觉学习笔记——PIL库的用法

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 这个 ...

  7. kubernets之横向伸缩pod与集群节点

    一  pod的自动伸缩容的应用背景 在面对负载并发过高的时候,我们或许希望能够提高RS,RC以及Deployment等的replicas的参数来增加pod的cpu,mem等,或者是通过提高每个容器的r ...

  8. Istio(一):服务网格和 Istio 概述

    目录 一.模块概览 二.微服务架构 三.服务网格概述 3.1 服务网格概述 3.2 为什么需要服务网格? 四.istio简介 4.1 Istio 简介 4.2 流量管理 4.3 可观察性 4.4 安全 ...

  9. windows安装mysql8(5分钟)

    1.下载 MySQL https://dev.mysql.com/downloads/mysql/ 下载完成后,解压缩到你的目录里. 2.配置 MySQL 的配置文件 创建一个文件,名称为:my.in ...

  10. EndNote里参考文献的期刊名显示错误怎么办?

      本文介绍EndNote文献管理软件导入文献引用时,期刊名称带有%J前缀从而不能正常显示的解决方法.   前期的文章中,我们多次介绍了文献管理软件EndNote的具体使用方法与技巧.而在使用EndN ...