iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > ASP.NET >EntityFramework管理并发
  • 549
分享到

EntityFramework管理并发

2024-04-02 19:04:59 549人浏览 安东尼
摘要

理解并发 并发管理解决的是允许多个实体同时更新,实际上这意味着允许多个用户同时在相同的数据上执行多个数据库操作。并发是在一个数据库上管理多个操作的一种方式,同时遵守了数据库操作的AC

理解并发

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

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

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

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

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

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

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

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

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

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

一、理解乐观并发

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

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

1、忽略冲突/强制更新

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

2、部分更新

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

3、警告/询问用户

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

4、拒绝更改

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

二、理解悲观并发

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

只读锁

更新锁。

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

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

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

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

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

三、使用EF实现乐观并发

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

1、新建控制台项目,项目名: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; }

    }
}

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

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,
                 }
                 );
        }
    }
}

3、数据库上下文定义如下

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);
        }
    }
}

4、实现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.用户乙更新这个实体的Amount
news2.Amount = 10m;
UpdateNews(news2);

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

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

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

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

从上面的截图可以看出,用户乙的请求成功了,而用户甲的更新丢失了。因此,从上面的代码不难看出,如果我们使用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,
      };
}

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

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

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

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

4、保存更改。

更新方法定义如下:

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();
            }
}

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

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

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

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

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

五、使用RowVersion实现并发

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

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

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

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

1668299584
public 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);
}

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

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

现在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();
}

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

从抛出的异常信息来看,很明显是抛出了和并发相关的异常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管理并发的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持编程网。

--结束END--

本文标题: EntityFramework管理并发

本文链接: https://www.lsjlt.com/news/141445.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

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

下载Word文档
猜你喜欢
  • EntityFramework管理并发
    理解并发 并发管理解决的是允许多个实体同时更新,实际上这意味着允许多个用户同时在相同的数据上执行多个数据库操作。并发是在一个数据库上管理多个操作的一种方式,同时遵守了数据库操作的AC...
    99+
    2022-11-13
  • EntityFramework Core解决并发详解
    话题(EntityFramework Core并发)对于并发问题这个话题相信大家并不陌生,当数据量比较大时这个时候我们就需要考虑并发,对于并发涉及到的内容也比较多,在EF Core中我们将并发分为几个小节来...
    99+
    2022-10-18
  • EntityFramework使用CodeFirst模式管理视图
    一、什么是视图 视图在RDBMS(关系型数据库管理系统)中扮演了一个重要的角色,它是将多个表的数据联结成一种看起来像是一张表的结构,但是没有提供持久化。因此,可以将视图看成是一个原生...
    99+
    2022-11-13
  • EntityFramework使用CodeFirst模式管理事务
    一、什么是事务 处理以数据为中心的应用时,另一个重要的话题是事务管理。ADO.NET为事务管理提供了一个非常干净和有效的API。因为EF运行在ADO.NET之上,所以EF可以使用AD...
    99+
    2022-11-13
  • EntityFramework使用CodeFirst模式管理数据库
    一、管理数据库连接 1、使用配置文件管理连接之约定 在数据库上下文类中,如果我们只继承了无参数的DbContext,并且在配置文件中创建了和数据库上下文类同名的连接字符串,那么EF会...
    99+
    2022-11-13
  • EntityFramework使用CodeFirst模式管理存储过程
    在EF中使用存储过程和使用视图是很相似的,一般会使用Database对象上的两个方法:SqlQuery和ExecuteSqlCommand。为了从存储过程中读取很多数据行,我们只需要...
    99+
    2022-11-13
  • Entity Framework管理并发的方法
    这篇“Entity Framework管理并发的方法”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Entity&n...
    99+
    2023-06-29
  • Django是否支持Git的并发管理?
    Django是一个流行的Python Web框架,它提供了强大的功能和易于使用的API,使得开发Web应用程序变得更加简单。随着团队规模的增加,代码的管理和版本控制变得越来越重要。Git是一种广泛使用的版本控制系统,它可以帮助团队有效地管...
    99+
    2023-10-16
    git django 并发
  • Java并发编程和Linux系统管理:如何管理日志文件并提高系统性能?
    随着互联网的发展,大量的数据需要被记录下来,而日志文件便成为了记录这些数据的重要手段。对于Java并发编程和Linux系统管理来说,如何管理日志文件并提高系统性能是一个非常重要的话题。在本文中,我们将探讨如何使用Java并发编程和Linu...
    99+
    2023-10-28
    并发 linux 日志
  • Python并发处理
    1.创建并销毁线程#!/usr/bin/python #code to execute in an independent thread import time def countdown(n):     while n > 0:...
    99+
    2023-01-31
    Python
  • PHP开发中如何处理接口并发请求和并发处理
    在实际的Web开发中,我们经常会遇到并发请求的情况。并发请求是指多个请求同时发送给服务器进行处理。如果我们的应用程序无法正确处理并发请求,就有可能导致数据不一致、性能下降等问题。本文将介绍如何在PHP开发中处理接口的并发请求和并发处理,并提...
    99+
    2023-10-21
    接口 并发处理 并发请求
  • ASP并发编程:如何管理不同的数据类型?
    在ASP编程中,我们经常需要处理不同类型的数据,例如数字、字符串、日期等等。在并发编程中,对于不同类型的数据的管理也变得尤为重要。本文将介绍如何在ASP并发编程中管理不同的数据类型,并附上相关的演示代码。 一、数字类型的管理 在ASP中,...
    99+
    2023-11-04
    并发 数据类型 教程
  • 从 Shell 到 ASP 框架:如何有效管理并发请求?
    在当今互联网时代,高并发请求已经成为了一个常见的问题。无论是在 Shell 脚本还是 ASP 框架中,如何有效地管理并发请求是一项非常重要的技能。在本文中,我们将探讨如何在 Shell 脚本和 ASP 框架中有效地管理并发请求。 一、Sh...
    99+
    2023-08-08
    框架 并发 shell
  • java高并发处理 java处理高并发的几种方法
    一、背景综述         并发就是可以使用多个线程或进程,同时处理(就是并发)不同的操作。         高并发的时候就是有很多用户在访问,导致系统数据不正确、糗事数据的现象。对于一些大型网站,比如门户网站,在面对大量用户访问、高并发...
    99+
    2023-09-22
    java
  • 《PHP并发编程:使用容器管理您的应用程序》?
    PHP并发编程:使用容器管理您的应用程序 在当今的互联网时代,大量用户同时访问一个网站或应用程序已经成为了一种常态。这就需要我们的应用程序能够支持高并发,以保证用户的使用体验。而在 PHP 中,实现高并发的方法之一就是使用容器。本文将会介绍...
    99+
    2023-10-02
    并发 教程 容器
  • 在Go语言中如何解决并发配置管理问题?
    在Go语言中如何解决并发配置管理问题?随着软件开发行业的迅猛发展,配置管理已经成为一个不可忽视的重要环节。在多线程编程中,如何安全并发地管理配置是一个常见的问题。本文将介绍如何通过使用Go语言提供的并发控制机制解决并发配置管理问题,并给出具...
    99+
    2023-10-22
    包括各种选项 变量和标志等。
  • Linux日志管理:如何优化Java并发编程的效率?
    Java并发编程是当今软件开发中不可避免的一部分,因为它可以大大提高程序的效率。然而,在高并发环境下,Java程序的运行效率也会受到许多因素的影响,例如内存使用、线程调度等。其中,日志管理是一个非常重要的因素,因为它可以帮助开发人员更好地...
    99+
    2023-10-28
    并发 linux 日志
  • 如何在Linux中使用Shell和Bash来管理并发任务?
    在Linux系统中,Shell和Bash是两个非常强大的命令行工具,它们可以帮助我们高效地管理并发任务。在本文中,我们将介绍如何使用Shell和Bash来实现任务的并发执行,以及如何管理这些任务的运行状态。 一、什么是并发任务 在计算机中,...
    99+
    2023-09-30
    并发 shell bash
  • RustAtomicsandLocks并发基础理解
    目录Rust 中的线程线程作用域所有权共享借用和数据竞争内部可变rust 中的线程安全 Send 和 Sync线程阻塞和唤醒Rust 中的线程 在 Rust 中,线程是轻量级的执行单...
    99+
    2023-02-27
    Rust 并发基础 Rust Atomics Locks
  • 高并发怎么处理
    高并发的处理:尽可能使网站上的页面采方法用静态页面。图片是最消耗资源的,将图片与页面进行分离。缓存、镜像、负载均衡。需要使用数据库集群或者库表散列。...
    99+
    2022-10-10
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作