我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Entity Framework管理并发的方法

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Entity Framework管理并发的方法

这篇“Entity Framework管理并发的方法”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Entity Framework管理并发的方法”文章吧。

理解并发

并发管理解决的是允许多个实体同时更新,实际上这意味着允许多个用户同时在相同的数据上执行多个数据库操作。并发是在一个数据库上管理多个操作的一种方式,同时遵守了数据库操作的ACID属性(原子性、一致性、隔离性和持久性)。

想象一下下面几种可能发生并发的场景:

用户甲和乙都尝试修改相同的实体。

用户甲和乙都尝试删除相同的实体。

用户甲正在尝试修改一个实体时,用户乙已经删除了该实体。

用户甲已经请求读取一个实体,用户乙读完该实体之后更新了它。

这些场景可能会潜在地产生错误的数据,试想,成百上千的用户同时尝试操作一个相同的实体,这种并发问题将会对系统带来更大的影响。

在处理与并发相关的问题时,一般有以下两种方法:

乐观并发:无论何时从数据库请求数据,数据都会被读取并保存到应用内存中。数据库级别没有放置任何显示锁。数据操作会按照数据层接收到的顺序执行。

悲观并发:无论何时从数据库请求数据,数据都会被读取,然后该数据上就会加锁,因此没有人能访问该数据。这会降低并发相关问题的机率,缺点是加锁是一个昂贵的操作,会降低整个应用程序的性能。

一、理解乐观并发

前面提到,在乐观并发中,无论何时从数据库请求数据,数据都会被读取并保存到应用内存中。数据库级别没有放置任何显式锁。因为这种方法没有添加显式锁,所以比悲观并发更具扩展性和灵活性。使用乐观并发,重点是如果发生了任何冲突,应用程序要亲自处理它们。最重要的是:使用乐观并发控制时,在应用中要有一个冲突解决策略,要让应用程序的用户知道他们的修改是否因为冲突的缘故没有持久化。乐观并发本质上是允许冲突发生,然后以一种适当的方式解决该冲突。

下面是处理冲突的策略例子。

忽略冲突/强制更新

这种策略是让所有的用户更改相同的数据集,然后所有的修改都会经过数据库,这就意味着数据库会显示最后一次更新的值。这种策略会导致潜在的数据丢失,因为许多用户的更改数据都丢失了,只有最后一个用户的更改是可见的。

部分更新

在这种情况中,我们也允许所有的更改,但是不会更新完整的行,只有特定用户拥有的列更新了。这就意味着,如果两个用户更新相同的记录但却不同的列,那么这两个更新都会成功,而且来自这两个用户的更改都是可见的。

警告/询问用户

当一个用户尝试更新一个记录时,但是该记录自从他读取之后已经被其他用户更改了,这时应用程序就会警告该用户该数据已经被其他用户更改了,然后询问他是否仍然要重写该数据还是首先检查已经更新的数据。

拒绝更改

当一个用户尝试更新一个记录时,但是该记录自从他读取之后已经被其他用户更改了,此时告诉该用户不允许更新该数据,因为数据已经被其他用户更新了。

二、理解悲观并发

悲观并发正好和乐观并发相反,悲观并发的目标是永远不让任何冲突发生。这是通过在使用记录之前就在记录上放置显式锁实现的。数据库记录上可以得到两种类型的锁:

只读锁

更新锁。

当把只读锁放到记录上时,应用程序只能读取该记录。如果应用程序要更新该记录,它必须要获取到该记录上的更新锁。如果记录上加了只读锁,那么该记录仍然能够被想要只读锁的请求使用。然而,如果需要更新锁,该请求必须等到所有的只读锁释放。同样,如果记录上加了更新锁,那么其他的请求不能再在这个记录上加锁,该请求必须等到已存在的更新锁释放才能加锁。

从前面的描述中,似乎悲观并发能解决所有跟并发相关的问题,因为我们不必在应用中处理这些问题。然而,事实上并不是这样的。在使用悲观并发管理之前,我们需要记住,使用悲观并发有很多问题和开销。下面是使用悲观并发面临的一些问题:

应用程序必须管理每个操作正在获取的所有锁。

加锁机制的内存需求会降低应用性能。

多个请求互相等待需要的锁,会增加死锁的可能性。由于这些原因,EF不直接支持悲观并发。如果想使用悲观并发的话,我们可以自定义数据库访问代码。此外,当使用悲观并发时,LINQ to Entities不会正确工作。

三、使用EF实现乐观并发

使用EF实现乐观并发有很多方法,接下来我们就看一下这些方法。

新建控制台项目,项目名:EFConcurrencyApp,新闻实体类定义如下:

using System;using System.Collections.Generic;using System.ComponentModel.DataAnnotations;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EFConcurrencyApp.Model{    public class News    {        public int Id { get; set; }        [MaxLength(100)]        public string Title { get; set; }        [MaxLength(30)]        public string Author { get; set; }        public string Content { get; set; }        public DateTime CreateTime { get; set; }        public decimal Amount { get; set; }    }}

使用数据迁移的方式生成数据库,并填充种子数据。

namespace EFConcurrencyApp.Migrations{    using EFConcurrencyApp.Model;    using System;    using System.Data.Entity;    using System.Data.Entity.Migrations;    using System.Linq;    internal sealed class Configuration : DbMigrationsConfiguration<EFConcurrencyApp.EF.EFDbContext>    {        public Configuration()        {            AutomaticMigrationsEnabled = false;        }        protected override void Seed(EFConcurrencyApp.EF.EFDbContext context)        {            //  This method will be called after migrating to the latest version.            //  You can use the DbSet<T>.AddOrUpdate() helper extension method            //  to avoid creating duplicate seed data.            context.News.AddOrUpdate(                 new Model.News()                 {                     Title = "美国大城市房价太贵 年轻人靠“众筹”买房",                     Author = "佚名",                     Content = "美国大城市房价太贵 年轻人靠“众筹”买房",                     CreateTime = DateTime.Now,                     Amount = 0,                 },                 new Model.News()                 {                     Title = "血腥扑杀流浪狗太残忍?那提高成本就是必须的代价",                     Author = "佚名",                     Content = "血腥扑杀流浪狗太残忍?那提高成本就是必须的代价",                     CreateTime = DateTime.Now,                     Amount = 0,                 },                 new Model.News()                 {                     Title = "iPhone 8或9月6日发布 售价或1100美元起",                     Author = "网络",                     Content = "iPhone 8或9月6日发布 售价或1100美元起",                     CreateTime = DateTime.Now,                     Amount = 0,                 }                 );        }    }}

数据库上下文定义如下

using EFConcurrencyApp.Model;using System;using System.Collections.Generic;using System.Data.Entity;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EFConcurrencyApp.EF{    public class EFDbContext:DbContext    {        public EFDbContext()            : base("name=AppConnection")        {        }        public DbSet<News> News { get; set; }        protected override void OnModelCreating(DbModelBuilder modelBuilder)        {            // 设置表名和主键            modelBuilder.Entity<News>().ToTable("News").HasKey(p => p.Id);            base.OnModelCreating(modelBuilder);        }    }}

实现EF的默认并发

先看一下EF默认是如何处理并发的,现在假设我们的应用程序要更新一个News的Amount值,那么我们首先需要实现这两个函数FindNews()和UpdateNews(),前者用于获取指定的News,后者用于更新指定News。

Program类里面定义的两个方法如下:

static News FindNews(int id){      using (var db = new EFDbContext())      {            return db.News.Find(id);      }}static void UpdateNews(News news){      using (var db = new EFDbContext())      {          db.Entry(news).State = EntityState.Modified;          db.SaveChanges();       }}

下面我们实现这样一个场景:有两个用户甲和乙都读取了同一个News实体,然后这两个用户都尝试更新这个实体的不同字段,比如甲更新Title字段,乙更新Author字段,代码如下:

//1.用户甲获取id=1的新闻var news1 = FindNews(1);//2.用户乙获取id=1的新闻var news2 = FindNews(1);//3.用户甲更新这个实体的新闻标题news1.Title = news1.Title + "(更新)";UpdateNews(news1);//4.用户乙更新这个实体的Amountnews2.Amount = 10m;UpdateNews(news2);

上面的代码尝试模拟了一种并发问题。现在,甲和乙两个用户都有相同的数据副本,然后尝试更新相同的记录。执行代码前,先看一下数据库中的数据:

Entity Framework管理并发的方法

为了测试,在执行第四步时打一个断点:

Entity Framework管理并发的方法

在断点之后的代码执行之前,去数据库看一下数据,可以看到用户甲的更新已经产生作用了:

Entity Framework管理并发的方法

继续执行代码,在看一下数据库中的数据发生了什么变化:

Entity Framework管理并发的方法

从上面的截图可以看出,用户乙的请求成功了,而用户甲的更新丢失了。因此,从上面的代码不难看出,如果我们使用EF更新整条数据,那么最后一个请求总会获得胜利,也就是说:最后一次请求的更新会覆盖之前所有请求的更新。

四、设计处理字段级别并发的应用

接下来,我们会看到如何编写处理字段级别并发问题的应用代码。这是设计方式的应用思想是:只有更新的字段才会在数据库中进行更改。这样就保证了如果多个用户正在更新不同的字段,所有的更改都可以持久化到数据库。

实现这个的关键是让该应用识别用户正在请求更新的所有列,然后为该用户有选择地更新那些字段。通过以下两个方法来实现:

取数据的方法:该方法会给我们一个原始模型的克隆,只有用户请求的属性会更新为新值。

更新的方法:它会检查原始请求模型的哪个属性值已经发生更改,然后在数据库中只更新那些值。

因此,首先需要创建一个简单的方法,该方法需要模型属性的值,然后会返回一个新的模型,该模型除了用户尝试更新的属性以外,其他的属性值都和原来的模型属性值相同。方法定义如下:

static News GetUpdatedNews(int id, string title, string author, decimal amount, string content, DateTime createTime){     return new News     {           Id = id,           Title = title,           Amount = amount,           Author = author,           Content = content,           CreateTime = createTime,      };}

下一步,需要更改更新的方法。该更新方法会实现下面更新数据的算法:

根据Id从数据库中检索最新的模型值。

检查原始模型和要更新的模型来找出更改属性的列表。

只更新步骤2中检索到的模型发生变化的属性。

保存更改。

更新方法定义如下:

static void UpdateNewsEnhanced(News originalNews, News newNews){            using (var db = new EFDbContext())            {                //从数据库中检索最新的模型                var news = db.News.Find(originalNews.Id);                //接下来检查用户修改的每个属性                if (originalNews.Title != newNews.Title)                {                    //将新值更新到数据库                    news.Title = newNews.Title;                }                if (originalNews.Content != newNews.Content)                {                    //将新值更新到数据库                    news.Content = newNews.Content;                }                if (originalNews.CreateTime != newNews.CreateTime)                {                    //将新值更新到数据库                    news.CreateTime = newNews.CreateTime;                }                if (originalNews.Amount != newNews.Amount)                {                    //将新值更新到数据库                    news.Amount = newNews.Amount;                }                if (originalNews.Author != newNews.Author)                {                    //将新值更新到数据库                    news.Author = newNews.Author;                }                // 持久化到数据库                db.SaveChanges();            }}

运行代码前,先查看数据库中的数据:

Entity Framework管理并发的方法

然后执行主程序代码,在执行第四步时打个断点:

Entity Framework管理并发的方法

再次查看数据库的数据,发现用户甲的操作已经执行了:

Entity Framework管理并发的方法

Entity Framework管理并发的方法

继续运行程序,再次查看数据库的数据,发现用户乙的操作也执行了:

Entity Framework管理并发的方法

Entity Framework管理并发的方法

从上面的截图看到,两个用户请求同一个实体的更新值都持久化到了数据库中。因此,如果用户更新不同的字段,该程序可以有效地处理并发更新了。但是如果多个用户同时更新相同的字段,那么这种方法仍然显示的是最后一次请求的值。虽然这种方式减少了一些并发相关的问题,但是这种方法意味着我们必须写大量代码来处理并发问题。后面我们会看到如何使用EF提供的机制来处理并发问题。

五、使用RowVersion实现并发

前面我们看到了EF默认如何处理并发(最后一次请求的数据更新成功),然后看到如果多个用户尝试更新不同的字段时,如何设计应用处理这些问题。接下来,我们看一下当多个用户更新相同的字段时,使用EF如何处理字段级更新。

EF让我们指定字段级并发,这样如果一个用户更新一个字段的同时,该字段已经被其他用户更新过了,就会抛出一个并发相关的异常。使用这种方法,当多个用户尝试更新相同的字段时,我们就可以更有效地处理并发相关的问题。

如果我们为多个字段使用了特定字段的并发,那么会降低应用性能,因为生成的SQL会更大,更加有效的方式就是使用RowVersion机制。RowVersion机制使用了一种数据库功能,每当更新行的时候,就会创建一个新的行值。

给News实体类添加一个属性:

1646473255public byte[] RowVersion { get; set; }

在数据库上下文中配置属性:

protected override void OnModelCreating(DbModelBuilder modelBuilder){      // 设置表名和主键      modelBuilder.Entity<News>().ToTable("News").HasKey(p => p.Id);      // 设置属性      modelBuilder.Entity<News>().Property(d => d.RowVersion).IsRowVersion();      base.OnModelCreating(modelBuilder);}

删除原先的数据库,然后重新生成数据库,数据库模式变为:

Entity Framework管理并发的方法

查看数据,RowVersion列显示的是二进制数据:

Entity Framework管理并发的方法

现在EF就会为并发控制追踪RowVersion列值。接下来尝试更新不同的列:

using (var context = new EFDbContext()){                var news = context.News.SingleOrDefault(p => p.Id == 1);                Console.WriteLine(string.Format("标题:{0} 打赏金额:{1} ", news.Title, news.Amount.ToString("C")));                context.Database.ExecuteSqlCommand(@"update news set                         amount = 229.95 where Id = @p0", news.Id);                news.Amount = 239.95M;                Console.WriteLine(string.Format("标题:{0} 打赏金额:{1} ", news.Title, news.Amount.ToString("C")));                context.SaveChanges();}

运行程序,会抛出下面的异常:

Entity Framework管理并发的方法

Entity Framework管理并发的方法

从抛出的异常信息来看,很明显是抛出了和并发相关的异常DbUpdateConcurrencyException,其他信息说明了自从实体加载以来,可能已经被修改或删除了。

无论何时一个用户尝试更新一条已经被其他用户更新的记录,都会获得异常DbUpdateConcurrencyException。

当实现并发时,我们总要编写异常处理的代码,给用户展示一个更友好的描述信息。上面的代码加上异常处理机制后修改如下:

using (var context = new EFDbContext()){      var news = context.News.SingleOrDefault(p => p.Id == 1);      Console.WriteLine(string.Format("标题:{0} 打赏金额:{1} ", news.Title, news.Amount.ToString("C")));      context.Database.ExecuteSqlCommand(string.Format(@"update News set                         Amount = 229.95 where Id = {0}", news.Id));      news.Amount = 239.95M;      Console.WriteLine(string.Format("标题:{0} 打赏金额:{1} ", news.Title, news.Amount.ToString("C")));      try      {            context.SaveChanges();      }      catch (DbUpdateConcurrencyException ex)      {            Console.WriteLine(string.Format("并发异常:{0}", ex.Message));      }      catch (Exception ex)      {            Console.WriteLine(string.Format("普通异常:{0}", ex.Message));      }}

此时,我们应该使用当前的数据库值更新数据,然后重新更改。作为开发者,如果我们想要协助用户的话,我们可以使用EF的DbEntityEntry类获取当前的数据库值。

using (var context = new EFDbContext()){      var news = context.News.SingleOrDefault(p => p.Id == 1);      Console.WriteLine(string.Format("标题:{0} 打赏金额:{1} ", news.Title, news.Amount.ToString("C")));              context.Database.ExecuteSqlCommand(string.Format(@"update News set        Amount = 229.95 where Id = {0}", news.Id));       news.Amount = 239.95M;       Console.WriteLine(string.Format("标题:{0} 打赏金额:{1} ", news.Title, news.Amount.ToString("C")));       try       {         context.SaveChanges();       }       catch (DbUpdateConcurrencyException ex)       {          // 使用这段代码会将Amount更新为239.95          var postEntry = context.Entry(news);          postEntry.OriginalValues.SetValues(postEntry.GetDatabaseValues());          context.SaveChanges();        }        catch (Exception ex)        {           Console.WriteLine(string.Format("普通异常:{0}", ex.Message));        }}

以上就是关于“Entity Framework管理并发的方法”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Entity Framework管理并发的方法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Entity Framework管理并发的方法

这篇“Entity Framework管理并发的方法”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Entity Framew
2023-06-29

Entity Framework使用Fluent API配置的方法

本篇内容介绍了“Entity Framework使用Fluent API配置的方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、配置主键
2023-06-29

Entity Framework Core延迟加载的方法怎么使用

本文小编为大家详细介绍“Entity Framework Core延迟加载的方法怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Entity Framework Core延迟加载的方法怎么使用”文章能帮助大家解决疑惑,下面跟着小编的
2023-06-29

php并发处理的方法有哪些

在PHP中处理并发的方法包括:1. 多线程:通过使用多个线程来同时执行多个任务。PHP本身并不支持多线程,但可以通过扩展如pthreads来实现多线程。2. 多进程:通过使用多个进程来同时执行多个任务。PHP可以使用fork函数创建子进程,
2023-08-24

springboot处理高并发的方法是什么

处理高并发的方法有以下几种:使用缓存:可以将一些热点数据或计算结果缓存起来,减少数据库或其他系统的访问压力。可以使用Spring Boot提供的缓存框架(如Spring Cache)来实现。使用消息队列:可以将请求放入消息队列中,然后异步处
2023-10-23

python使用期物处理并发的方法

这篇文章主要介绍“python使用期物处理并发的方法”,在日常操作中,相信很多人在python使用期物处理并发的方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”python使用期物处理并发的方法”的疑惑有所
2023-07-02

java并发处理数据的方法是什么

Java中处理并发数据的方法有以下几种:1. 使用synchronized关键字:synchronized关键字可以用于方法或代码块的前面,它可以确保在同一时刻只有一个线程可以访问被synchronized修饰的方法或代码块,从而保证数据的
2023-10-11

laravel高并发处理方法有哪些

Laravel 是一个流行的 PHP 框架,用于构建高性能的 Web 应用程序。在面对高并发处理时,可以采取以下方法:使用缓存:Laravel 提供了缓存机制,可以将常用的数据缓存在内存中,减少数据库查询的次数,提高性能。可以使用 Redi
2023-10-23

android多线程并发处理的方法是什么

在Android中,有多种方法处理多线程并发。以下是一些常用的方法:1. AsyncTask:AsyncTask是一种简单的异步任务处理机制。它允许在后台线程中执行耗时操作,并在主线程中更新UI。AsyncTask有三个关键方法:onPre
2023-08-12

android多线程并发处理的方法有哪些

在Android中,可以使用以下几种方法实现多线程并发处理:1. 使用Thread类:可以通过继承Thread类或使用Runnable接口创建多个线程对象,并在run()方法中实现并发处理逻辑。2. 使用AsyncTask类:它是一个封装了
2023-08-19

java多线程并发处理的方法是什么

Java多线程并发处理的方法有以下几种:1. 继承Thread类:创建一个继承自Thread类的子类,重写run方法来定义线程需要执行的任务,然后创建该子类的对象并调用start方法启动线程。2. 实现Runnable接口:创建一个实现了R
2023-08-24

java多线程并发处理的方法有哪些

Java提供了多种方法来实现多线程并发处理:1. 继承Thread类:通过继承Thread类,重写run()方法,并在该方法中实现需要并发处理的逻辑。然后创建Thread对象,并调用start()方法启动线程。2. 实现Runnable接口
2023-09-22

java处理高并发请求的方法是什么

Java处理高并发请求的方法有很多种,以下是一些常用的方法:使用线程池:可以使用Java中的线程池技术来管理并发请求。通过创建固定大小的线程池,可以控制同时处理的请求数量,避免系统资源被过多的请求耗尽。使用消息队列:可以使用消息队列来缓冲请
2023-10-25

Go并发的方法有哪些

这篇文章主要介绍了Go并发的方法有哪些的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Go并发的方法有哪些文章都会有所收获,下面我们一起来看看吧。一、goroutine1、协程(Coroutine)Golang
2023-06-29

Python3实现并发检验代理池地址的方法

本文实例讲述了Python3实现并发检验代理池地址的方法。分享给大家供大家参考,具体如下:#encoding=utf-8 #author: walker #date: 2016-04-14 #summary: 用协程/线程池并发检验代理有效
2022-06-04

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录