EntityFramework Core解决并发详解
前言
对过年已经无感,不过还是有很多闲暇时间来学学东西和多陪陪爸妈,这一点是极好的,好了,本节我们来讲讲EntityFramework Core中的并发问题。
话题(EntityFramework Core并发)
对于并发问题这个话题相信大家并不陌生,当数据量比较大时这个时候我们就需要考虑并发,对于并发涉及到的内容也比较多,在EF Core中我们将并发分为几个小节来陈述,让大家看起来也不太累,也容易接受,我们由浅入深。首先我们看下给出的Blog实体类。
public class Blog : IEntityBase
{
public int Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public ICollection<Post> Posts { get; set; }
}
对于在VS2015中依赖注入仓储我们就不再叙述,比较简单,我们看下控制器中的两个方法,一个是渲染数据,一个是更新数据的方法,如下:
public class HomeController : Controller
{
private IBlogRepository _blogRepository;
public HomeController(IBlogRepository blogRepository)
{
_blogRepository = blogRepository;
}
public IActionResult Index()
{
var blog = _blogRepository.GetSingle(d => d.Id == );
return View(blog);
} [HttpPost]
public IActionResult Index(Blog obj)
{
try
{
_blogRepository.Update(obj);
_blogRepository.Commit();
}
catch (Exception ex)
{
ModelState.AddModelError("", ex.Message);
}
return View(obj);
}
}
视图渲染数据如下:
@using StudyEFCore.Model.Entities
@model Blog
<html>
<head>
<title></title>
</head>
<body>
@using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
<table border="1" cellpadding="10">
<tr>
<td>博客ID :</td>
<td>
@Html.TextBoxFor(m => m.Id,
new { @readonly = "readonly" })
</td>
</tr>
<tr>
<td>博客名称 :</td>
<td>@Html.TextBoxFor(m => m.Name)</td>
</tr>
<tr>
<td>博客地址:</td>
<td>@Html.TextBoxFor(m => m.Url)</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="更新" />
</td>
</tr>
</table>
}
@Html.ValidationSummary()
</body>
</html>
最终在页面上渲染的数据如下:

接下来我们演示下如何引起并发问题,如下:

上述我们通过在视图页面更新值后然后在SaveChanges之前打断点,然后我们在数据库中改变其值,再来SaveChanges此时会报异常,错误信息如下:
Database operation expected to affect 1 row(s) but actually affected 0 row(s).
Data may have been modified or deleted since entities were loaded.
See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
因为在我们页面上改变其值后未进行SaveChanges,但是此时我们修改了Name的值,接着再来SaveChanges,此时报上述错误也就是我们本节所说的并发问题。既然出现了这样的问题,那么我们在EF Core中该如何解决出现的并发问题呢?在这里我们有两种方式,我们一一来陈述。
EF Core并发解决方案一(并发Token)
既然要讲并发Token,那么在此之前我们需要讲讲并发Token到底是怎样工作的,当我们对属性标识为并发Token,当我们从数据库中加载其值时,此时对应的属性的并发Token也就通过上下文而分配,当对分配的并发Token属性的相同的值进行了更新或者删除,此时会强制该属性的并发Token去进行检测,它会去检测影响的行数量,如果并发已经匹配到了,然后一行将被更新到,如果该值在数据库中已经被更新,那么将没有数据行会被更新。对于更新或者删除通过在WHERE条件上包括并发Token。接下来我们对要更新的Name将其设置为并发Token,如下:
public class BlogMap : EntityMappingConfiguration<Blog>
{
public override void Map(EntityTypeBuilder<Blog> b)
{
b.ToTable("Blog");
b.HasKey(k => k.Id);
b.Property(p => p.Name).IsConcurrencyToken();
b.Property(p => p.Url);
b.HasMany(p => p.Posts).WithOne(p => p.Blog).HasForeignKey(p => p.BlogId);
}
}
当我们进行如上设置后再来迁移更新模型,最终还是会抛出如下异常:
Database operation expected to affect row(s) but actually affected row(s).
Data may have been modified or deleted since entities were loaded.
See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
接下来我们再来看看解决并发而设置行版本的情况。
EF Core并发解决方案二(行版本)
当我们在插入或者更新时都会产生一个新的timestamp,这个属性也会被当做一个并发Token来对待,它会确保当我们更新值时但是其值已经被修改过时一定会如上所述抛出异常。那么怎么使用行版本呢,(我们只讲Fluent API关于Data Annotations请自行查找资料)在实体中定义如下属性:
public byte[] RowVersion { get; set; }
接着对该属性进行如下配置。
b.Property(p => p.RowVersion).IsConcurrencyToken().ValueGeneratedOnAddOrUpdate();
当我们再次进行如上演示时肯定会抛出同样的异常信息。
上述两种从本质上都未能解决在EF Core中的并发问题只是做了基础的铺垫,那么我们到底该如何做才能解决并发问题呢,请继续往下看。
解析EF Core并发冲突
我们通过三种设置来解析EF Core中的并发冲突,如下:
当前值(Current values):试图将当前修改的值写入到到数据库。
原始值(Original values):在未做任何修改时的需要从数据库中检索到的值。
数据值(Database values):当前保存在数据库中的值。
由于并发会抛出异常,所以我们需要 在SaveChanges时在并发冲突所产生的异常中来进行解决,并发异常呈现在 DbUpdateConcurrencyException 类中,我们只需要在此并发异常类解决即可。比如上述我们需要修改Name的值,我们做了基础的铺垫,设置了并发Token。但是还是会引发并发异常,未能解决问题,这个只是解决并发异常的前提,由于我们利用的仓储来操作数据,但是并发异常会利用到EF上下文,所以我们额外定义接口,直接通过上下文来操作,如下我们定义一个接口
public interface IBlogRepository : IEntityBaseRepository<Blog>
{
void UpdateBlog(Blog blog);
}
解决并发异常通过EF上下文来操作。
public class BlogRepository : EntityBaseRepository<Blog>,
IBlogRepository
{
private EFCoreContext _efCoreContext;
public BlogRepository(EFCoreContext efCoreContext) : base(efCoreContext)
{
_efCoreContext = efCoreContext;
} public void UpdateBlog(Blog blog)
{
try
{
_efCoreContext.Set<Blog>().Update(blog);
_efCoreContext.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Blog)
{
var databaseEntity = _efCoreContext.Set<Blog>().AsNoTracking().Single(p => p.Id == ((Blog)entry.Entity).Id);
var databaseEntry = _efCoreContext.Entry(databaseEntity); 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; // TODO: Logic to decide which value should be written to database
var propertyName = property.Name;
if (propertyName == "Name")
{
// Update original values to
entry.Property(property.Name).OriginalValue = databaseEntry.Property(property.Name).CurrentValue;
break;
}
}
}
else
{
throw new NotSupportedException("Don't know how to handle concurrency conflicts for " + entry.Metadata.Name);
}
} // Retry the save operation
_efCoreContext.SaveChanges();
}
}
}
上述则是通用解决并发异常的办法,我们只是注意上述表明的TODO逻辑,我们需要得到并发的属性,然后再来更新其值即可,我们对于Name会产生并发,所以遍历实体属性时获取到Name,然后更新其值即可,简单粗暴,完胜。我们看如下演示。

上述我们将Name修改为efcoreefcore,在SaveChanges前修改数据库中的Name,接着再来进行SaveChanges时,此时肯定会走并发异常,我们在并发异常中进行处理,最终我们能够很清楚的看到最终数据库中的Name更新为efcoreefcore,我们在最后重试一次在一定程度上可以保证能够解决并发。
总结
本节我们比较详细的讲解了EntityFramework Core中的并发问题以及该如何解决,到这里算是基本结束,我才发现在项目当中未经测试我居然用错了,明天去修改修改,这里算是一个稍微详细的讲解吧,如果进行压力测试不知道结果会怎样,后续进行压力测试若有进一步的进展再来完善,到时再来更新EF Core并发后续,好了,不早了,晚安。
EntityFramework Core解决并发详解的更多相关文章
- EntityFramework Core映射关系详解
前言 Hello,开始回归开始每周更新一到两篇博客,本节我们回归下EF Core基础,来讲述EF Core中到底是如何映射的,废话少说,我们开始. One-Many Relationship(一对多关 ...
- 补知识:EntityFramework Core映射关系详解
前言 本节我们回归下EF Core基础,来讲述EF Core中到底是如何映射的,废话少说,我们开始. One-Many Relationship(一对多关系) 首先我们从最简单的一对多关系说起,我们给 ...
- net core体系-web应用程序-4net core2.0大白话带你入门-5asp.net core环境变量详解
asp.net core环境变量详解 环境变量详解 Windows操作系统的环境变量在哪设置应该都知道了. Linux(centos版本)的环境变量在/etc/profile里面进行设置.用户级的 ...
- Linux core dump file详解
Linux core dump file详解 http://www.cnblogs.com/langqi250/archive/2013/03/05/2944931.html
- 基于ORA-12170 TNS 连接超时解决办法详解
转自原文 基于ORA-12170 TNS 连接超时解决办法详解 1.开始----程序-----Oracle------配置和移植工具-----Net Manager----本地----服务命名---o ...
- 【转】asp.net core环境变量详解
asp.net core环境变量详解 环境变量详解 Windows操作系统的环境变量在哪设置应该都知道了. Linux(centos版本)的环境变量在/etc/profile里面进行设置.用户级的环境 ...
- EntityFramework Core高并发深挖详解,一纸长文,你准备好了吗?
前言 之前有关EF并发探讨过几次,但是呢,博主感觉还是有问题,为什么会觉得有问题,其实就是理解不够透彻罢了,于是在项目中都是用的存储过程或者SQL语句来实现,利用放假时间好好补补EF Core并发的问 ...
- asp.net core环境变量详解
环境变量详解 Windows操作系统的环境变量在哪设置应该都知道了. Linux(centos版本)的环境变量在/etc/profile里面进行设置.用户级的环境变量在其它文件里面,不多说了,有兴趣的 ...
- MongoDB via Dotnet Core数据映射详解
用好数据映射,MongoDB via Dotnet Core开发变会成一件超级快乐的事. 一.前言 MongoDB这几年已经成为NoSQL的头部数据库. 由于MongoDB free schema ...
随机推荐
- CodeForces 610B Vika and Squares
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using ...
- Java模拟post提交表单数据
package test; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOExcep ...
- CentOS x 64 MooseFS 学习
一.MFS 简介.... MooseFS(Moose File System,mfs)是一种分布式文件系统,它将数据分布在网络中的 不同服务器上,支持FUSE,客户端可以作为一个 普通的Unix 文件 ...
- nginx location配置(URL)
语法规则: location [=|~|~*|^~] /uri/ { … }= 表示精确匹配,这个优先级也是最高的^~ 表示uri以某个常规字符串开头,理解为匹配 url路径即可.nginx不对url ...
- (简单) POJ 1847 Tram,Dijkstra。
Description Tram network in Zagreb consists of a number of intersections and rails connecting some o ...
- 对float的理解
从IE6下的双边距引出 对一个div设置float:left;同时设置了margin-left:100px时在IE6下会出现双边距. 有两种解决办法: 1,推荐办法.加display:inline 2 ...
- cocos2d中box2d讲解一
在游戏中我们经常要加入物理碰撞等和物理有关的内容,在游戏中加入物理引擎可以使我们的游戏更加真实,为玩家展示一个更真实的世界,cocos2d-x支持两个物理引擎Box2d和Chipmunk,本文介绍bo ...
- ECshop中的session机制理解
ECshop中的session机制理解 在网上找了发现都是来之一人之手,也没有用自己的话去解释,这里我就抛砖引玉,发表一下自己的意见,还希望能得到各界人士的指导批评! 此session机制不需 ...
- Python3基础 frozenset() 创建一个不可更改的集合
镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.-------------------------------------- ...
- Add Strings Leetcode
Given two non-negative integers num1 and num2 represented as string, return the sum of num1 and num2 ...