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 == 1);            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此时会报异常,错误信息如下:

 
See http:

因为在我们页面上改变其值后未进行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:

接下来我们再来看看解决并发而设置行版本的情况。

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,我们在最后重试一次在一定程度上可以保证能够解决并发。

时间: 2024-11-05 22:33:04

EntityFramework Core解决并发详解的相关文章

EF并发详解

EntityFramework Core高并发深挖详解,一纸长文,你准备好看完了吗? 前言 之前有关EF并发探讨过几次,但是呢,博主感觉还是有问题,为什么会觉得有问题,其实就是理解不够透彻罢了,于是在项目中都是用的存储过程或者SQL语句来实现,利用放假时间好好补补EF Core并发的问题,本文比较长,请耐心点看. EntityFramework Core并发初级版初探 关于并发无非就两种:乐观并发和悲观并发,悲观并发简言之则是当客户端对数据库中同一值进行修改时会造成阻塞,而乐观并发则任何客户端都

google无法访问 2014解决方法详解

方法二 寻找可用IP地址 IP地址一:http://74.125.205.147/ 即可访问谷歌 转:http://www.newasp.net/tech/89292.html 以是我个人实测的:配合 Chrome 浏览器.GoaAgent v3.1.8.SwitchySharp.crx.SwitchyOptions.bak 就可以访问google 而且搜索打开的页面很快. google无法访问 2014解决方法详解,布布扣,bubuko.com

Linux core dump file详解

Linux core dump file详解 http://www.cnblogs.com/langqi250/archive/2013/03/05/2944931.html

【转】asp.net core环境变量详解

环境变量详解 Windows操作系统的环境变量在哪设置应该都知道了. Linux(centos版本)的环境变量在/etc/profile里面进行设置.用户级的环境变量在其它文件里面,不多说了,有兴趣的可以网上查下. 当我们的应用程序发布到生产环境时,如果用到了环境变量,就需要对服务器操作系统的环境变量进行设置.这里只是进行提前说明. 设置环境变量 环境变量可以在launchSettings.json文件里面设置,也可以右键项目->属性->调试->环境变量进行设置.这两种方式的内容是同步的

Microsoft SQL Server中的事务与并发详解

本篇索引: 1.事务 2.锁定和阻塞 3.隔离级别 4.死锁 一.事务 1.1 事务的概念 事务是作为单个工作单元而执行的一系列操作,比如查询和修改数据等. 事务是数据库并发控制的基本单位,一条或者一组语句要么全部成功,对数据库中的某些数据成功修改; 要么全部不成功,数据库中的数据还原到这些语句执行之前的样子. 比如网上订火车票,要么你定票成功,余票显示就减一张; 要么你定票失败获取取消订票,余票的数量还是那么多.不允许出现你订票成功了,余票没有减少或者你取消订票了,余票显示却少了一张的这种情况

ASP.NET Core真实管道详解[2]:Server是如何完成针对请求的监听、接收与响应的【上】

Server是ASP .NET Core管道的第一个节点,负责完整请求的监听和接收,最终对请求的响应同样也由它完成.Server是我们对所有实现了IServer接口的所有类型以及对应对象的统称,如下面的代码片段所示,这个接口具有一个只读属性Features返回描述自身特性集合的FeatureCollection对象,另一个Start方法用于启动服务器. 1: public interface IServer : IDisposable 2: { 3: IFeatureCollection Fea

Python学习之异常重试解决方法详解

本文和大家分享的是在使用python 进行数据抓取中,异常重试相关解决办法,一起来看看吧,希望对大家学习python有所帮助. 在做数据抓取的时候,经常遇到由于网络问题导致的程序保存,先前只是记录了错误内容,并对错误内容进行后期处理. 原先的流程: defcrawl_page(url): pass deflog_error(url): pass url = "" try: crawl_page(url) except: log_error(url) 改进后的流程: attempts =

ASP.NET Core真实管道详解[1]

ASP.NET Core管道虽然在结构组成上显得非常简单,但是在具体实现上却涉及到太多的对象,所以我们在 <ASP.NET Core管道深度剖析[共4篇]> 中围绕着一个经过极度简化的模拟管道讲述了真实管道构建的方式以及处理HTTP请求的流程.在这个系列 中,我们会还原构建模拟管道时刻意舍弃和改写的部分,想读者朋友们呈现一个真是的HTTP请求处理管道. ASP.NET Core 的请求处理管道由一个Server和一组有序排列的中间件构成,前者仅仅完成基本的请求监听.接收和响应的工作,请求接收之

Dede后台验证码不显示解决方法详解(dedecms 5.7)

今天朋友问我他本地与服务器上安装了dedecms5.7无法显示验证码,一般这种情况很少见,一般情况就是服务器设置问题,还有临时目录的权限问题 Dede后台验证码不显示或不正常分三种情况,下面来逐一分析 Dede后台验证码不显示情况一 Dede后台验证码不显示情况二 Dede后台验证码不显示情况三 通用解决方案---取消后台验证码功能 因为没有验证码 不能进后台 所以修改php文件源代码: 方法一: 打开dede/login.php 找到如下代码 if(($validate=='' || $val