tnblog
首页
视频
资源
登录

.net core ef处理并发冲突

6200人阅读 2021/12/21 14:31 总访问:827588 评论:0 收藏:0 手机
分类: .net

当某用户显示实体数据以对其进行编辑,而另一用户在上一用户的更改写入数据库之前更新同一实体的数据时,会发生并发冲突。 如果不启用此类冲突的检测,则最后更新数据库的人员将覆盖其他用户的更改。 在许多应用程序中,此风险是可接受的:如果用户很少或更新很少,或者一些更改被覆盖并不重要,则并发编程可能弊大于利。 在此情况下,不必配置应用程序来处理并发冲突。

悲观并发

如果应用程序确实需要防止并发情况下出现意外数据丢失,一种方法是使用数据库锁定。 这称为悲观并发。 例如,在从数据库读取一行内容之前,请求锁定为只读或更新访问。 如果将一行锁定为更新访问,则其他用户无法将该行锁定为只读或更新访问,因为他们得到的是正在更改的数据的副本。 如果将一行锁定为只读访问,则其他人也可将其锁定为只读访问,但不能进行更新。

管理锁定有缺点。 编程可能很复杂。 它需要大量的数据库管理资源,且随着应用程序用户数量的增加,可能会导致性能问题。 由于这些原因,并不是所有的数据库管理系统都支持悲观并发。 Entity Framework Core 未提供对它的内置支持。

检测并发冲突之跟踪列

数据库表中包含一个可用于确定某行更改时间的跟踪列。 然后可配置 Entity Framework,将该列包含在 SQL Update 或 Delete 命令的 Where 子句中。

跟踪列的数据类型通常是 rowversion。 rowversion 值是一个序列号,该编号随着每次行的更新递增。 在 Update 或 Delete 命令中,Where 子句包含跟踪列的原始值(原始行版本)。 如果正在更新的行已被其他用户更改,则 rowversion 列中的值与原始值不同,这导致 Update 或 Delete 语句由于 Where 子句而找不到要更新的行。 当 Entity Framework 发现 Update 或 Delete 命令没有更新行(即受影响的行数为零)时,便将其解释为并发冲突。

检测并发冲突之匹配原始值

配置 Entity Framework,在 Update 或 Delete 命令的 Where 子句中包含表中每个列的原始值。

与第一个选项一样,如果行自第一次读取后发生更改,Where 子句将不返回要更新的行,Entity Framework 会将其解释为并发冲突。 对于包含许多列的数据库表,此方法可能导致非常多的 Where 子句,并且可能需要维持大量的状态。 如前所述,维持大量的状态会影响应用程序的性能。 因此通常不建议使用此方法。

跟踪列解决并发冲突


添加跟踪属性:

  1. [Timestamp]
  2.  public byte[] RowVersion { getset; }


使用 Fluent API的 IsConcurrencyToken 方法:

  1. protected override void OnModelCreating(ModelBuilder modelBuilder)
  2. {
  3.     modelBuilder.Entity<Student>().Property(p => p.RowVersion).IsConcurrencyToken();
  4. }


更新返回编辑页面方法:

  1. public async Task<IActionResult> Edit(int? id)
  2. {
  3.     if (id == null)
  4.     {
  5.         return NotFound();
  6.     }
  7.     //var student = await _context.Students.FindAsync(id);
  8.     var student = await _context.Students.AsNoTracking().FirstOrDefaultAsync(m => m.ID == id);
  9.     if (student == null)
  10.     {
  11.         return NotFound();
  12.     }
  13.     return View(student);
  14. }

更新编辑方法:

  1. //并发冲突
  2. [HttpPost]
  3. [ValidateAntiForgeryToken]
  4. public async Task<IActionResult> Edit(int? id, byte[] rowVersion)
  5. {
  6.     if (id == null)
  7.     {
  8.         return NotFound();
  9.     }
  10.     var studentsToUpdate = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);
  11.     // 在调用 SaveChanges 之前,必须将该原始 RowVersion 属性值置于实体的 OriginalValues 集合中
  12.     _context.Entry(studentsToUpdate).Property("RowVersion").OriginalValue = rowVersion;
  13.     if (await TryUpdateModelAsync<Student>(
  14.         studentsToUpdate,
  15.         "",
  16.         s => s.FirstMidName, s => s.LastName, s => s.Age, s => s.EnrollmentDate))
  17.     {
  18.         try
  19.         {
  20.             /*
  21.                 当 Entity Framework 创建 SQL UPDATE 命令时,该命令将包含一个 WHERE 子句,用于查找具有原始 RowVersion 值的行。 
  22.             如果没有行受到 UPDATE 命令影响(没有行具有原始 RowVersion 值),则 Entity Framework 会引发 DbUpdateConcurrencyException 异常。
  23.                 */
  24.             await _context.SaveChangesAsync();
  25.             return RedirectToAction(nameof(Index));
  26.         }
  27.         catch (DbUpdateConcurrencyException ex)
  28.         {
  29.             var exceptionEntry = ex.Entries.Single();
  30.             var clientValues = (Student)exceptionEntry.Entity;
  31.             var databaseEntry = exceptionEntry.GetDatabaseValues();
  32.             if (databaseEntry == null)
  33.             {
  34.                 ModelState.AddModelError(string.Empty,
  35.                     "Unable to save changes. The department was deleted by another user.");
  36.             }
  37.             else
  38.             {
  39.                 //获取数据库中的数据,因为这里处理并发的方式是存储优先
  40.                 var databaseValues = (Student)databaseEntry.ToObject();
  41.                 //和数据库的值进行比较,用于给用户进行明确的提示,让用户知道目前数据库中是存储的是什么值
  42.                 if (databaseValues.FirstMidName != clientValues.FirstMidName)
  43.                 {
  44.                     ModelState.AddModelError("FirstMidName"$"Current value: {databaseValues.FirstMidName}");
  45.                 }
  46.                 if (databaseValues.LastName != clientValues.LastName)
  47.                 {
  48.                     ModelState.AddModelError("LastName"$"Current value: {databaseValues.LastName:c}");
  49.                 }
  50.                 if (databaseValues.Age != clientValues.Age)
  51.                 {
  52.                     ModelState.AddModelError("Age"$"Current value: {databaseValues.Age:d}");
  53.                 }
  54.                 if (databaseValues.EnrollmentDate != clientValues.EnrollmentDate)
  55.                 {
  56.                     ModelState.AddModelError("EnrollmentDate"$"Current value: {databaseValues.EnrollmentDate:d}");
  57.                 }
  58.                 //提示出现了并发,数据在打开与点击保存过程被其他用户修改过
  59.                 ModelState.AddModelError(string.Empty, "您尝试编辑的记录在获得原始值后被另一个用户修改。"
  60.                         + "编辑操作已取消,数据库中的当前值已显示。"
  61.                         + "如果仍要编辑此记录,请再次单击“保存”按钮。否则,请单击“返回列表”超链接。 ");
  62.                 //把跟踪属性修改成和数据库一样的,这样第二次更新的时候就可以成功了,当然如果第二次也不想用户更新成功也可以不设置
  63.                 studentsToUpdate.RowVersion = (byte[])databaseValues.RowVersion;
  64.                 ModelState.Remove("RowVersion");
  65.             }
  66.         }
  67.     }
  68.     return View(studentsToUpdate);
  69. }

更新编辑视图,添加隐藏字段以保存 RowVersion 属性值,紧跟在 ID属性的隐藏字段后面:

  1. <input type="hidden" asp-for="ID" />
  2. <input type="hidden" asp-for="RowVersion" />


测试并发冲突,同时打开两个页面进行修改:





欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739。有需要软件开发,或者学习软件技术的朋友可以和我联系~(Q:815170684)

评价

.net core ef json序列化循环引用问题

直接贴代码:services.AddControllersWithViews() .AddNewtonsoftJson(options=&gt; { options.SerializerSettings.Cont...

.net core ef数据库迁移,以及迁移命令详解。code first。在不删除表的情况下保持同步

.NET CORE EF 数据库迁移需要的依赖Install-Package Microsoft.EntityFrameworkCore.SqlServer -version 3.1.1 Install-Pa...

.net core ef Fluent API配置外键

和以前的写法变了一点点,以前HasRequired换成HasOne即可。以前的写法://1:先配置多的一方(按照多对一方式来配置) model...

net core 使用 EF Code First

下面这些内容很老了看这篇:https://www.tnblog.net/aojiancc2/article/details/5365 项目使用多层,把数据库访问...

.net mvc分部页,.net core分部页

.net分部页的三种方式第一种:@Html.Partial(&quot;_分部页&quot;)第二种:@{ Html.RenderPartial(&quot;分部页&quot;);}...

StackExchange.Redis操作redis(net core支持)

官方git开源地址https://github.com/StackExchange/StackExchange.Redis官方文档在docs里边都是官方的文档通过nuget命令下...

.net core 使用session

tip:net core 2.2后可以直接启用session了,不用在自己添加一次session依赖,本身就添加了使用nuget添加引用Microsoft.AspN...

通俗易懂,什么是.net?什么是.net Framework?什么是.net core?

朋友圈@蓝羽 看到一篇文章写的太详细太通俗了,搬过来细细看完,保证你对.NET有个新的认识理解原文地址:https://www.cnblo...

asp.net core2.0 依赖注入 AddTransient与AddScoped的区别

asp.net core主要提供了三种依赖注入的方式其中AddTransient与AddSingleton比较好区别AddTransient瞬时模式:每次都获取一...

.net core 使用 Kestrel

Kestrel介绍 Kestrel是一个基于libuv的跨平台web服务器 在.net core项目中就可以不一定要发布在iis下面了Kestrel体验可以使...

net core中使用cookie

net core中可以使用传统的cookie也可以使用加密的cookieNET CORE中使用传统cookie设置:HttpContext.Response.Cookies.Appe...

net core项目结构简单分析

一:wwwrootwwwroot用于存放网站的静态资源,例如css,js,图片与相关的前端插件等lib主要是第三方的插件,例如微软默认引用...

net core使用EF之DB First

一.新建一个.net core的MVC项目新建好项目后,不能像以前一样直接在新建项中添加ef了,需要用命令在添加ef的依赖二.使用Nug...

.net core使用requestresponse下载文件下载excel等

使用request获取内容net core中request没有直接的索引方法,需要点里边的Query,或者formstringbase64=Request.Form[&quot;f...

iframe自适应高度与配合net core使用

去掉iframe边框frameborder=&quot;0&quot;去掉滚动条scrolling=&quot;no&quot;iframe 自适应高度如果内容是固定的,那么就...
这一生多幸运赶上过你.
排名
8
文章
222
粉丝
7
评论
7
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术