学习ASP.NET Core Razor 编程系列目录

学习ASP.NET Core Razor 编程系列一

学习ASP.NET Core Razor 编程系列二——添加一个实体

学习ASP.NET Core Razor 编程系列三——创建数据表及创建项目基本页面

学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面

学习ASP.NET Core Razor 编程系列五——Asp.Net Core Razor新建模板页面

学习ASP.NET Core Razor 编程系列六——数据库初始化

学习ASP.NET Core Razor 编程系列七——修改列表页面

学习ASP.NET Core Razor 编程系列八——并发处理

学习ASP.NET Core Razor 编程系列九——增加查询功能

学习ASP.NET Core Razor 编程系列十——添加新字段

学习ASP.NET Core Razor 编程系列十一——把新字段更新到数据库

学习ASP.NET Core Razor 编程系列十二——在页面中增加校验

学习ASP.NET Core Razor 编程系列十三——文件上传功能(一)

学习ASP.NET Core Razor 编程系列十四——文件上传功能(二)

学习ASP.NET Core Razor 编程系列十五——文件上传功能(三)

学习ASP.NET Core Razor 编程系列十六——排序

学习ASP.NET Core Razor 编程系列十七——分组

在文章(学习ASP.NET Core Razor 编程系列八——并发处理)中对于并发错误,我们只是简单粗暴的进行了异常捕获,然后抛出了异常。在本文中我们来看两个解决并发的方法。

乐观并发的解决方案有以下三种:

1) 可以跟踪用户已修改的属性,并仅更新数据库中相应的列。

在这种情况下,数据不会丢失。 两个用户更新了不同的字段内容(例如:书名与出版社)。下次有人浏览书籍信息时,将看到书名和出版社两个人的更改。 这种更新方法可以减少导致数据丢失的冲突数。这种方法需要维持重要状态,以便跟踪所有数据库值与当前值,增加了应用复杂,可能会影响应用性能。通常不适用于 Web 应用。

2) 可让后提交的用户更改覆盖之前用户提交的更改。

这种方法称为“客户端优先”或“最后一个优先”方案。 (客户端的所有值优先于数据存储的值。)如果不对并发处理进行任何编码,则自动执行“客户端优先”。

3) 可以阻止在数据库中更新后一用户提交的更改。

这种方法,需要显示错误信息,显示当前数据和数据库中的数据,允许用户重新修改,并保存。这称为“存储优先”方案。 (数据存储值优先于客户端提交的值。)

一、客户端优先

接下去我们来看看“客户端优先”方案。 此方法确保后一用户的提交为准,覆盖数据库中的数据。

乐观并发允许发生并发冲突,并在并发冲突发生时作出正确反应。 例如,管理员访问用书籍信息编辑页面,将“Publishing”字段值修改为“清华大学出版社”。

1.首先,我们使用Visual Studio 2017打开Books\Edit.cshmtl.cs文件,看一下OnPostAsync()方法,代码如下。如下图。

 public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page(); }
_context.Attach(Book).State = EntityState.Modified; try
{
await _context.SaveChangesAsync(); }
catch (DbUpdateConcurrencyException)
{
if (!_context.Book.Any(e => e.ID == Book.ID)) {
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}

2.在Visual Studio 2017中按F5运行应用程序。在浏览器中浏览书籍信息,并在书籍列表页面中选择一条书籍信息。我们假设有两个用户要对此条书籍信息进行编辑。首先是管理员,对此条书籍信息修改了“Publishing”的信息。如下图。

3.在管理员单击“Save”按钮之前,Test用户访问了相同页面,并将“出版日期”修改为了“2018-01-08”。如下图。

4.Test用户先单击“保存”,并在浏览器的书籍信息列表页面中看到了他修改的出版日期数据保存到了数据库。如下图。

5.此时,管理员单击“编辑”页面上的“保存”,但页面的上的“出版日期”还是“2018-01-13”,按照“客户端优化”规则会把Test用户的修改覆盖掉。如下图。

二、存储优先

接下去我们来看看“存储优先”方案。 此方法可确保用户在未收到警报时不会覆盖任何更改。

    首先我们来了解三组值:

  • “当前值”是应用程序尝试写入数据库的值。
  • “原始值”是在进行任何编辑之前最初从数据库中检索的值。
  • “数据库值”是当前存储在数据库中的值。

处理并发冲突的常规方法是:

1)在 SaveChanges 期间捕获 DbUpdateConcurrencyException。

2)使用 DbUpdateConcurrencyException.Entries 为受影响的实体准备一组新更改。

3)刷新并发令牌的原始值以反映数据库中的当前值。

4)重试该过程,直到不发生任何冲突。

下面的示例,使用时间戳作为行级版本号。

1. 在Visual Studio 2017的“解决方案资源管理器”中使用鼠标左键双击打开 Models /Book.cs文件, 对User实体添加跟踪属性RowVersion,并在其上添加Timestamp特性。代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks; namespace RazorMvcBooks.Models
{ public class Book
{ public int ID { get; set; }
[Required]
[StringLength(, MinimumLength = )] public string Name { get; set; }
[Display(Name = "出版日期")]
[DataType(DataType.Date)] public DateTime ReleaseDate { get; set; }
[Range(,)]
[DataType(DataType.Currency)]
public decimal Price { get; set; } public string Author { get; set; } [ Required]
public string Publishing { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
} }

2.在Visual Studio 2017中选择“菜单>Nuget包管理器>程序包管理器控制台”,然后在打开的程序包管理器控制台依次执行以下命令

Add-Migration RowVer
Update-Database

3.在SQL Server Management Studio中查看Book表。如下图。

4.在Visual Studio 2017的“解决方案资源管理器”中使用鼠标左键双击打开 Pages/Books/Edit.cshtml.cs文件,对OnPostAsync方法进行修改。Entity Framework Core 使用包含原始 RowVersion 值的 WHERE 子句生成 SQL UPDATE 命令。如果没有行受到 UPDATE 命令影响(没有行具有原始 RowVersion 值),将引发 DbUpdateConcurrencyException 异常。代码如下:

  public async Task<IActionResult> OnPostAsync()
{ if (!ModelState.IsValid)
{
return Page();
} var updBook = _context.Book.AsNoTracking().Where(u => u.ID == Book.ID).First();
// 如果为null,则当前用户信息已经被 删除
if (updBook == null)
{
return HandDeleteBook();
} _context.Attach(Book).State = EntityState.Modified;
if (await TryUpdateModelAsync<Book>(
Book,
"Book",
s => s.Name, s =>s.Publishing, s => s.ReleaseDate, s => s.Price)) { try
{ await _context.SaveChangesAsync();
return RedirectToPage("./Index");
} catch (DbUpdateConcurrencyException ex)
{ var exceptionEntry = ex.Entries.Single();
var clientValues = (Book)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues(); if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "保存失败!.当前用户信息已经被删除");
return Page();
} var dbValues = (Book)databaseEntry.ToObject();
setDbErrorMessage(dbValues, clientValues, _context);
//用数据库中的 RowVersion 值设置为当前实体对象客户端界面中的RowVersion值。 用户下次单击“保存”时,将仅捕获最后一次显示编辑页后发生的并发错误。 Book.RowVersion = (byte[])dbValues.RowVersion;
//ModelState 具有旧的 RowVersion 值,因此需使用 ModelState.Remove 语句。 在 Razor 页面中,
//当两者都存在时,字段的 ModelState 值优于模型属性值。 ModelState.Remove("Book.RowVersion");
}
} return Page();
} private PageResult HandDeleteBook()
{
Book deletedDepartment = new Book();
ModelState.AddModelError(string.Empty,
"保存失败!.当前书籍信息已经被删除!");
return Page(); }

6.在Edit.cshtml.cs文件,添加setDbErrorMessage方法。为每列添加自定义错误消息,当这些列中的数据库值与客户端界面上的值不同时,给出相应的错误信息。代码如下:

   private void setDbErrorMessage(Book dbValues,
Book clientValues, BookContext context)
{ if (dbValues.Name != clientValues.Name)
{
ModelState.AddModelError("Book.Name",
$"数据库值: {dbValues.Name}");
} if (dbValues.Publishing != clientValues.Publishing)
{
ModelState.AddModelError("Book.Publishing",
$"数据库值: {dbValues.Publishing}");
} if (dbValues.ReleaseDate != clientValues.ReleaseDate)
{
ModelState.AddModelError("Book.ReleaseDate",
$"数据库值: {dbValues.ReleaseDate}");
}
if (dbValues.Price != clientValues.Price)
{ ModelState.AddModelError("Book.Price",
$"数据库值: {dbValues.Price}");
}
ModelState.AddModelError(string.Empty,"您尝试编辑的书籍信息记录被另一个用户修改了。编辑操作被取消,"
+ "数据库中的当前值已经显示。如果仍想编辑此记录,请单击“保存”按钮。"); }

7.在Visual Studio 2017的“解决方案资源管理器”中使用鼠标左键双击打开 Pages/Books/Edit.cshtml文件,  <form method="post">标签下面添加添加隐藏的行版本。必须添加 RowVersion,以便回发绑定值。

<input type="hidden" asp-for="Book.RowVersion" />

8.在Visual Studio 2017中按F5运行应用程序。使用两个浏览器打开同一条书籍信息记录进行编辑,此时两个浏览器显示的书籍信息是一样的。浏览器1中的书籍信息界面。在修改了“Publishing”的数据由“清华大学出版社”修改为“机械工业出版社”,然后点击“Save”按钮。如下图。

9.在浏览器中单击“保存”之后,浏览器会自动跳转到书籍信息列表页面中看到了所修改的“Publishing”数据保存到了数据库。如下图。

10.在第二个浏览器中,修改“出版日期”的值,由“2018-01-13”改为“2018-01-08”。如下图。

11.然后使用单击“ Save”按钮。此时由于客户端界面上的信息与数据库中的值不一样,所以会出现错误提示信息。如下图。

12. 把“Publishing”修改为“机械工业出版社”,再次单击“保存”,将第二个浏览器中输入的值保存到数据库。 浏览器自动跳转到书籍信息列表,可以看到保存的值。如下图。

13.当然如果你不做任何修改,再次点击保存,也会把当前页面上的数据保存到数据库中。如下图。

学习ASP.NET Core Razor 编程系列十八——并发解决方案的更多相关文章

  1. 学习ASP.NET Core Razor 编程系列十九——分页

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  2. 学习ASP.NET Core Razor 编程系列十六——排序

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  3. 学习ASP.NET Core Razor 编程系列十五——文件上传功能(三)

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  4. 学习ASP.NET Core Razor 编程系列十四——文件上传功能(二)

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  5. 学习ASP.NET Core Razor 编程系列十二——在页面中增加校验

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  6. 学习ASP.NET Core Razor 编程系列十——添加新字段

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  7. 学习ASP.NET Core Razor 编程系列十七——分组

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  8. 学习ASP.NET Core Razor 编程系列十三——文件上传功能(一)

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  9. 学习ASP.NET Core Razor 编程系列十一——把新字段更新到数据库

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

随机推荐

  1. JAVA经典算法40题(原题+分析)之原题

    JAVA经典算法40题(上) [程序1]   题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第四个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? [程 ...

  2. SQL Server事务 事务日志

    事务 (SQL Server) 一.事务概念    事务是一种机制.是一种操作序列,它包含了一组数据库操作命令,这组命令要么全部执行,要么全部不执行.因此事务是一个不可分割的工作逻辑单元.在数据库系统 ...

  3. EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下?

    前言 这个问题从未遇见过,是一位前辈问我EF Core内存泄漏问题时我才去深入探讨这个问题,刚开始我比较惊讶,居然还有这种问题,然后就有了本文,直接拿前辈的示例代码并稍加修改成就了此文,希望对在自学E ...

  4. 前端随笔 - JavaScript中的闭包

    前阵子重新复习了一下js基础知识,第一篇博客就以分享闭包心得为开始吧. 首先,要理解闭包,就必须要了解一个概念:作用域链. 作用域链 作用域代表着可访问变量的集合,变量分为全局变量和局部变量两种,在函 ...

  5. 史上最最靠谱,又双叒叒简单的基于MSXML的XML解析指南-C++

    目录 史上最最靠谱,又双叒叒简单的基于MSXML的XML解析指南 流程设计 xml信息有哪几种读取形式(xml文件或wchar) 如何选取节点,and取节点属性有哪些方法? IXMLDOMNode与I ...

  6. [区块链] 拜占庭将军问题 [BFT]

    背景: 拜占庭将军问题很多人可能听过,但不知道具体是什么意思.那么究竟什么是拜占庭将军问题呢? 本文从最通俗的故事讲起,并对该问题进行抽象,并告诉大家拜占庭将军问题为什么在区块链领域作为一个重点研究问 ...

  7. 我不是bug神(JVM问题排查)

    Story background 回望2018年12月,这也许是程序员们日夜不得安宁的日子,皆因各种前线的系统使用者都需要冲业绩等原因,往往在这个时候会向系统同时写入海量的数据,当我们的应用或者数据库 ...

  8. Java集合详解8:Java集合类细节精讲

    今天我们来探索一下Java集合类中的一些技术细节.主要是对一些比较容易被遗漏和误解的知识点做一些讲解和补充.可能不全面,还请谅解. 本文参考:http://cmsblogs.com/?cat=5 具体 ...

  9. Struts2的拦截器配置

    1:引入默认的时间拦截器 <!-- 引入拦截器和引入拦截器栈一样的语法 --> <interceptor-ref name="defaultStack">& ...

  10. 笔记:Java虚拟机运行时数据区

    Java虚拟机在执行Java程序的过程中会把它管的内存划分为以下若干个不同的区域: 1.程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器:由于Java虚拟机的 ...