前言

Entity Framework Core 2.0更新也已经有一段时间了,园子里也有不少的文章..

本文主要是浅析一下Entity Framework Core的并发处理方式.

1.常见的并发处理策略

要了解如何处理并发,就要知道并发的一般处理策略

悲观并发策略

悲观并发策略,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守悲观的态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观并发策略大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的巨大开销,特别是对长事务而言,这样的开销在大量的并发情况下往往无法承受。

乐观并发策略

乐观并发策略,一般是基于数据版本 Version记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现.读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。需要注意的是,乐观并发策略机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性.

本篇就是讲解,如何在我们的Entity Framework Core中来使用和自定义我们的并发策略

2.Entity Framework Core并发令牌

要使用Entity Framework Core中的并发策略,就需要使用我们的并发令牌(ConcurrencyCheck)

在Entity Framework Core中,并发的默认处理方式是无视并发冲突的,任何修改语句在条件符合的情况下,都可以修改成功.

在高并发的情况下这种处理方式,肯定会给我们的数据库带来很多脏数据,所以,Entity Framework Core提供了并发令牌(ConcurrencyCheck)这个特性.

如果一个属性被配置为并发令牌,则EF将在保存这条记录时,会检查没有其他用户修改过数据库中的这个属性的值。EF使用了乐观并发策略,这意味着它将假定值没有改变,并尝试保存数据,但如果发现值已更改,则抛出异常。

举个例子,我们有一个用户类(User),我们配置 User中的 Name为并发令牌。这意味着,如果一个用户试图保存一个有些变化的 User,但另一个用户已经改变了 Name那么将抛出一个异常。这在应用中一般是可取的,以便我们的应用程序可以提示用户,在保存他们的改变之前,以确保此记录仍然代表同一个姓名的人。

2.1并发令牌在EF中工作的原理

当我们配置User中的Name为令牌的时候,EF会将并发令牌包含在Where、Update或delete命令的子句中并检查受影响的行数来实现验证。如果并发令牌仍然匹配,则一行将被更新。如果数据库中的值已更改,则不会更新任何行。

比如,当我们设置Name为并发令牌,然后通过ID来修改User的PassWord的时候,EF会生成如下的修改语句:

UPDATE [User] SET [PassWord] = @p1
WHERE [ID] = @p0 AND [Name] = @p2;

当然,这时候,Name不匹配了,受影响的行数返回为0.

2.2并发令牌的使用约定

属性默认不被配置为并发令牌。

2.3并发令牌的使用方式

1.直接使用特性,如下配置UserName为并发令牌:

public partial class UserTable
{
public int Id { get; set; }
[ConcurrencyCheck]
public string UserName { get; set; }
public string PassWord { get; set; }
public int? ClassId { get; set; }
}

2.使用FluentAPI配置属性为并发令牌

class MyContext : DbContext
{
public DbSet<UserTable> People { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserTable>()
.Property(p => p.UserName)
.IsConcurrencyToken();
}
}

以上2种方式,效果是一样的.

2.4使用时间戳和行级版本号

我们知道,SQL Server给我们提供了时间戳的属性(当然,几乎所有的关系数据库都有这个).下面举个SQL Server的例子

我们加一个时间戳字段为TimestampV,加上特性Timestamp,实体代码如下:

  public partial class UserTable
{
public int Id { get; set; } public string UserName { get; set; }
public string PassWord { get; set; }
public int? ClassId { get; set; } public ClassTable Class { get; set; } [Timestamp]
public byte[] TimestampV { get; set; }
}

CodeFrist生成的表如下:

自动帮我们生成的Timestamp类型的一个字段.

配置时间戳属性的方式也有2种,上面已经说了一种..特性的..

同样我们也可以使用Fluent API配置属性为时间戳,代码如下:

class MyContext : DbContext
{
public DbSet<UserTable> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserTable>()
.Property(p => p.TimestampV)
.ValueGeneratedOnAddOrUpdate()
.IsConcurrencyToken();
}
}
3.如何根据需求自定义处理并发冲突

上面,我们已经配置好了需要并发处理的表,也配置好了相关的特性,下面我们就来讲讲如何使用它.

使用之前,我们先来了解一下,并发过程中所产生的3个值,也是我们需要处理的3个值

1.当前值是应用程序尝试写入数据库的值。

2.原始值是在进行任何编辑之前最初从数据库检索的值。

3.数据库值是当前存储在数据库中的值。

当我们配置好上面的并发令牌时,在EF执行SaveChanges()操作并产生并发的时候,我们会得到DbUpdateConcurrencyException的异常信息,(注意:在不配置并发令牌时,这个异常一般不会触发)

前面,我们已经讲过乐观并发策略是一种性能较高,也比较实用的处理方式,所以我们就通过时间戳来处理这个并发的问题.

示例测试代码如下:

 public void Test()
{
//重新创建数据库,并新增一条数据
using (var context = new School_TestContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated(); context.UserTable.Add(new UserTable { UserName = "John", PassWord = "Doe" });
context.SaveChanges();
} using (var context = new School_TestContext())
{
// 修改id为1的用户名称
var person = context.UserTable.Single(p => p.Id == );
person.UserName = "555-555-5555"; // 直接通过访问数据库来修改同一条数据 (这里是为了模拟并发)
context.Database.ExecuteSqlCommand("UPDATE dbo.UserTable SET UserName = 'Jane' WHERE ID = 1"); try
{
//尝试保存修改
int a = context.SaveChanges();
}
//获取并发异常
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is UserTable)
{
var databaseEntity = context.UserTable.AsNoTracking().Single(p => p.Id == ((UserTable)entry.Entity).Id);
var databaseEntry = context.Entry(databaseEntity); //当前上下文时间戳
var date = ConvertToTimeSpanString(entry.Property("TimestampV").CurrentValue);
var dateint = Int32.Parse(date, System.Globalization.NumberStyles.HexNumber); //数据库时间戳
var datebase = ConvertToTimeSpanString(databaseEntry.Property("TimestampV").CurrentValue);
var dateint2 = Int32.Parse(datebase, System.Globalization.NumberStyles.HexNumber);
//如果当前上下文时间戳与数据库相同,或者更加新,则使用当前
if (dateint >= dateint2)
{
foreach (var property in entry.Metadata.GetProperties())
{
//当前值
var proposedValue = entry.Property(property.Name).CurrentValue; //原始值
var originalValue = entry.Property(property.Name).OriginalValue; //数据库值
var databaseValue = databaseEntry.Property(property.Name).CurrentValue; //更新当前值
entry.Property(property.Name).CurrentValue = proposedValue; //更新原始值来保证修改成功
entry.Property(property.Name).OriginalValue = databaseEntry.Property(property.Name).CurrentValue;
// 尝试重新保存数据
int aa = context.SaveChanges();
}
}
}
else
{
throw new NotSupportedException("无法处理并发," + entry.Metadata.Name);
}
} }
} }

执行这段代码,会发现,符合我们乐观并发策略的要求.

值为最后修改的UserName,为Jane,如图:

解释一下,为何最终结果为Jane.

首先,我们添加了一条UserName为John的数据,我们在上下文中修改它为"555-555-5555",

这时候,产生并发,另一个上下文在这个SaveChang之前,就执行完成了,把值修改为了Jane,所以EF通过并发令牌发现匹配失败.则会触发异常.

在异常中,我们将当前上下文的版本号和数据库现有的版本号进行对比,发现当前上下文的版本号为过期数据,则不更新,并返回失败.

请仔细看代码中的注释.

注意:这里的例子是根据乐观并发处理策略要进行处理的.你可以根据你的业务,来任意处理当前值,原始值和数据库值,选择你需要的值保存.

写在最后

.net core已经2.0版本了,Asp.net Core也2.0了..EFcore也2.0了..功能已经越来越强大,越来越完善.完全可以投入生产了.园子里对这些新技术也很关注,真的...我感觉很棒..从未如此的棒!!!!

浅析Entity Framework Core中的并发处理的更多相关文章

  1. 《浅析Entity Framework Core中的并发处理》引起的思考

    看到一篇关于EF并发处理的文章,http://www.cnblogs.com/GuZhenYin/p/7761352.html,突然觉得为什么常见业务中为什么很少做并发方面的考虑.结合过去的项目,这样 ...

  2. 悲观并发 乐观并发 Entity Framework Core中的并发处理

    悲观并发策略 A用户发起一个请求   开启了事务 查询到了某一条数据 进行修改     在A提交事务之前 其他人都不能对这条数据进行修改 这种策略最常见的一个问题就是死锁  比如A修改X记录,B修改Y ...

  3. 如何处理Entity Framework / Entity Framework Core中的DbUpdateConcurrencyException异常(转载)

    1. Concurrency的作用 场景有个修改用户的页面功能,我们有一条数据User, ID是1的这个User的年龄是20, 性别是female(数据库中的原始数据)正确的该User的年龄是25, ...

  4. Entity Framework Core中的数据迁移命令

    使用程序包管理控制台输入命令. 数据迁移命令: Add-Migration  对比当前数据库和模型的差异,生成相应的代码,使数据库和模型匹配的. Remove-Migration 删除上次的迁移 Sc ...

  5. Entity Framework Core生成的存储过程在MySQL中需要进行处理及PMC中的常用命令

    在使用Entity Framework Core生成MySQL数据库脚本,对于生成的存储过程,在执行的过程中出现错误,需要在存储过程前面添加 delimiter // 附:可以使用Visual Stu ...

  6. 全自动迁移数据库的实现 (Fluent NHibernate, Entity Framework Core)

    在开发涉及到数据库的程序时,常会遇到一开始设计的结构不能满足需求需要再添加新字段或新表的情况,这时就需要进行数据库迁移. 实现数据库迁移有很多种办法,从手动管理各个版本的ddl脚本,到实现自己的mig ...

  7. Entity Framework Core 执行SQL语句和存储过程

    无论ORM有多么强大,总会出现一些特殊的情况,它无法满足我们的要求.在这篇文章中,我们介绍几种执行SQL的方法. 表结构 在具体内容开始之前,我们先简单说明一下要使用的表结构. public clas ...

  8. 使用ASP.NET Core MVC 和 Entity Framework Core 开发一个CRUD(增删改查)的应用程序

    使用ASP.NET Core MVC 和 Entity Framework Core 开发一个CRUD(增删改查)的应用程序 不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻 ...

  9. Entity Framework Core 使用HiLo生成主键

    #cnblogs_post_body.cnblogs-markdown p img { max-width: 95%; } HiLo是在NHiernate中生成主键的一种方式,不过现在我们可以在Ent ...

随机推荐

  1. Jacoco远程统计tomcat服务(Windows系统)的代码覆盖率

    Jacoco远程统计tomcat服务(Windows系统)的代码覆盖率 2017-09-21 目录 1 Jacoco的安装和设置  1.1 什么是Jacoco?  1.2 Jacoco安装  1.3 ...

  2. PHP多进程编程pcntl_fork解

    其实PHP是支持并发的,只是平时很少使用而已.平时使用最多的应该是使用PHP-FMP调度php进程了吧. 但是,PHP的使用并不局限于做Web,我们完全也可以使用PHP来进行系统工具类的编程,做监控或 ...

  3. grunt学习笔记1 理论知识

    你需要检查js语法错误,然后再去压缩js代码.如果这两步你都去手动操作,会耗费很多成本.Grunt就能让你省去这些手动操作的成本. “—save-dev”的意思是,在当前目录安装grunt的同时,顺便 ...

  4. Https系列之二:https的SSL证书在服务器端的部署,基于tomcat,spring boot

    Https系列会在下面几篇文章中分别作介绍: 一:https的简单介绍及SSL证书的生成二:https的SSL证书在服务器端的部署,基于tomcat,spring boot三:让服务器同时支持http ...

  5. java基础——java.util.ConcurrentModificationException

    在编写代码的时候,有时候会遇到List里有符合条件的的对象,就移除改对象! 但是这种操作如:使用了 List 的remove,会导致一些很严重的问题! 如下这段代码使用ArrayList: @Test ...

  6. 第4章 同步控制 Synchronization ----互斥器(Mutexes)

    Win32 的 Mutex 用途和 critical section 非常类似,但是它牺牲速度以增加弹性.或许你已经猜到了,mutex 是 MUTual EXclusion 的缩写.一个时间内只能够有 ...

  7. mybatis 架构

    官网地址:http://code.google.com/p/mybatis/ 版本:mybatis 3.2.3 生成工具:mybatis-generator-core-1.3.2-bundle.zip ...

  8. Linux入门之常用命令(10)软连接 硬链接

    在Linux系统中,内核为每一个新创建的文件分配一个Inode(索引结点),每个文件都有一个惟一的inode号.文件属性保存在索引结点里,在访问文件时,索引结点被复制到内存在,从而实现文件的快速访问. ...

  9. 基于Apache axis2开发Java Web服务

    1.安装配置axis2环境 1)下载axis2-1.4.1-war(发布webservice)和axis2-1.4.1-bin.zip(webservice调用使用的各种包) 下载好后把axis2-1 ...

  10. Python协程深入理解

    从语法上来看,协程和生成器类似,都是定义体中包含yield关键字的函数.yield在协程中的用法: 在协程中yield通常出现在表达式的右边,例如:datum = yield,可以产出值,也可以不产出 ...