Entity Framework 处理并发

在以前的两个教程你对关联数据进行了操作。本教程展示如何处理并发性。您将创建工作与各Department实体的 web 页和页,编辑和删除Department实体将处理并发错误。下面的插图显示索引和删除的页面,包括一些如果发生并发冲突,则显示的消息。

并发冲突

当一个用户要编辑它,显示实体数据,然后另一个用户更新相同的实体数据第一个用户的更改写入到数据库之前,将发生并发冲突。如果您不启用此类冲突检测,最后谁更新数据库覆盖其他用户的更改。在许多应用中,这种风险是可以接受: 如果有几个用户或一些更新,或者如果不是真的很重要,如果覆盖了一些变化,并发性编程的费用可能超过其益处。在这种情况下,您不需要配置应用程序以处理并发冲突。

保守式并发 (锁定)

如果您的应用程序确实需要防止数据意外丢失在并发性的场景中,做到这一点的一种方法是使用数据库锁。这就被所谓保守式并发。例如,从数据库中读取的行之前,你请求锁定为只读或更新访问权限。如果您锁定行更新访问权限,允许没有其他用户锁定该行要么为只读或更新访问权限,因为他们会得到正在更改的数据的一个副本。如果您锁定的只读访问权限的行,别人也可以锁定它为只读访问权限而不是更新。

管理锁定也有缺点。它可以是复杂的程序。它需要大量的数据库管理资源,它可能导致性能问题的应用程序的用户数增加了 (也就是说,它并不很好地扩展)。基于这些原因,并不是所有的数据库管理系统支持保守式并发。实体框架提供了,没有内置的支持,本教程不会告诉你如何实现它。

开放式并发

保守式并发的替代方案是开放式并发。开放式并发意味着允许并发冲突发生,然后适当地反应,如果他们这样做。例如,John运行部门编辑页面,改变为英语系的预算金额从 $ 350000.00改为 $0.00。

John单击保存之前,Jane运行相同的页面,并更改开始日期字段从 2007/9/1 改为 2013/8/8。

John第一次单击保存,看到他的变化,当浏览器返回到索引页上,然后Jane再单击保存。下一步会发生什么取决于你如何处理并发冲突。一些选项包括以下内容:

  • 你可以跟踪用户已修改的属性和更新仅在数据库中相应的列。在示例场景中,没有的数据将会丢失,因为不同的属性由两个用户更新。在下一次有人浏览English department时,他们就会看到John和Jane的修改 —一开始日期 2013/8/8 和预算零美元。

    这种更新方法可以减少冲突,可能会导致数据丢失,但它不能避免 如果有两个人更改到同一个实体的属性 数据丢失。 Entity Framework是否用这种方式取决于您如何实现您更新代码。它往往是不实际的Web应用程序,因为它需要维护大量的状态,以及新值保持一个实体的所有原始属性值的轨道。维护大量的状态会影响应用程序性能,因为它需要消耗服务器资源,或者必须包括在 web 页 (例如,在隐藏字段)。

  • 您可以让Jane的更改覆盖John的更改。在下一次有人浏览English department时,他们会看到 2013/8/8 和还原的 $ 350,000.00 值。这就被所谓Client Wins  Last in Wins的场景。(客户端的值将优先于在数据存储区中的是什么。)因为在这一节,导言中指出,如果你不做任何编码的并发处理,这会自动发生。

  • 你可以阻止Jane的更改对数据库的更新。通常情况下,如果她仍然想要保存,会显示一条错误消息,显示她的数据的当前状态和允许她重新打开页面修改。这就被所谓一个 Store Wins的场景。(数据存储区值将优先于提交的客户端的值)。在本教程中,您将实现 Store Wins方案。此方法确保没有更改将被覆盖没有用户,注意到了发生了什么事。

检测并发冲突

可以通过实体框架将引发的OptimisticConcurrencyException异常处理来解决冲突。为了知道什么时候会引发这些异常,实体框架必须能够检测到冲突。因此,你适当地必须配置数据库和数据模型。启用冲突检测的一些选项包括以下内容:

  • 在数据库表中,包含可用于记录确定何时更改行的跟踪列。你可以配置实体框架UpdateDelete命令中包含WhereSQL 语句中列的。

    跟踪列的数据类型通常是rowversion.rowversion值是一个已更新的行每次递增的顺序编号。在UpdateDelete的命令中, Where子句包括跟踪列 (原始行版本) 的原始值。如果由另一个用户更改了正在更新的行,rowversion列中的值是不同于原始值,因此UpdateDelete语句无法找到要更新的Where子句的行。当实体框架发现没有行被更新过的UpdateDelete命令 (那就是,当受影响的行数为零) 时,它将这解释为并发冲突。

  • 配置实体框架可以在UpdateDelete命令的Where子句中的表中包含每个列的原始值。

    在第一个选项,如果在首次读取时发现有任何的改变, Where子句不返回行更新,而实体框架解释作为一个并发冲突。对于有多个列的数据库表,此方法可能会导致非常大的Where子句,并可以要求你保持大量的状态。因为它消耗服务器资源,或者必须包括在 web 页本身,如前文所述,保持大量的状态可以影响应用程序性能可能。因此一般不建议使用此方法,并在本教程中不使用此方法。

    如果你想要实现这种并发性的方法,你有来标记您想要跟踪并发性,通过将ConcurrencyCheck属性添加到他们的实体中的所有非主键属性。这种变化使实体框架可以在UPDATE语句的WHERESQL 子句中包括的所有列。

在本教程的其余部分会添加到Department实体跟踪属性的rowversion、 创建一个控制器和视图,和测试,以验证一切工作正常。

将开放式并发属性添加到Department实体

Models\Department.cs,添加一个名为 RowVersion的跟踪属性:

public class Department
{
public int DepartmentID { get; set; } [StringLength(50, MinimumLength = 3)]
public string Name { get; set; } [DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; } [DataType(DataType.Date)]
public DateTime StartDate { get; set; } [Display(Name = "Administrator")]
public int? InstructorID { get; set; } [Timestamp]
public byte[] RowVersion { get; set; } public virtual Instructor Administrator { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}

Timestamp属性指定此列的UpdateDelete命令发送到数据库的Where子句中。该属性称为Timestamp,因为以前版本的 SQL Server 使用 SQLTimestamp数据类型之前 SQLrowversion替换它。.Net 类型为 rowversion是一个字节数组。如果你喜欢使用 fluent API,你可以使用IsConcurrencyToken方法来指定跟踪的属性,如下面的示例所示:

modelBuilder.Entity<Department>()
.Property(p => p.RowVersion).IsConcurrencyToken();

通过添加一个属性更改的数据库模型,所以你需要做另一次迁移。在程序包管理器控制台 (PMC) 中,输入以下命令:

Add-Migration RowVersion 
Update-Database

创建一个部控制器

创建Department控制器和视图的相同的方式,你的其他控制器,使用以下设置:

Controllers\DepartmentController.cs,添加using语句:

using System.Data.Entity.Infrastructure;

更改"LastName"为"FullName"任何一个此文件 (四个点) 以便部管理员下拉列表将包含讲师的完整名称,而不是只是最后的名字。

ViewBag.InstructorID = new SelectList(db.Instructors, "InstructorID", "FullName");

HttpPost Edit方法的现有代码替换为以下代码:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
[Bind(Include = "DepartmentID, Name, Budget, StartDate, RowVersion, InstructorID")]
Department department)
{
try
{
if (ModelState.IsValid)
{
db.Entry(department).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var clientValues = (Department)entry.Entity;
var databaseValues = (Department)entry.GetDatabaseValues().ToObject(); if (databaseValues.Name != clientValues.Name)
ModelState.AddModelError("Name", "Current value: "
+ databaseValues.Name);
if (databaseValues.Budget != clientValues.Budget)
ModelState.AddModelError("Budget", "Current value: "
+ String.Format("{0:c}", databaseValues.Budget));
if (databaseValues.StartDate != clientValues.StartDate)
ModelState.AddModelError("StartDate", "Current value: "
+ String.Format("{0:d}", databaseValues.StartDate));
if (databaseValues.InstructorID != clientValues.InstructorID)
ModelState.AddModelError("InstructorID", "Current value: "
+ db.Instructors.Find(databaseValues.InstructorID).FullName);
ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again. Otherwise click the Back to List hyperlink.");
department.RowVersion = databaseValues.RowVersion;
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name after DataException and add a line here to write a log.
ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
} ViewBag.InstructorID = new SelectList(db.Instructors, "InstructorID", "FullName", department.InstructorID);
return View(department);
}

视图将隐藏字段中存储的原始RowVersion值。当模型联编程序创建department实例时,该对象也将有原始RowVersion属性值和其他属性的新值,这是作为由用户编辑页上输入。然后当实体框架创建一个 SQLUPDATE命令,命令将包括WHERE子句的行看起来具有原始RowVersion 值。

如果没有任何行受到UPDATE命令 (即没有行有原始 RowVersion值),实体框架将引发DbUpdateConcurrencyException异常,和catch块中的代码从异常对象中获取受影响的Department实体。此实体已从数据库中读取的值和由用户输入的新值:

var entry = ex.Entries.Single();
var clientValues = (Department)entry.Entity;
var databaseValues = (Department)entry.GetDatabaseValues().ToObject();

接下来,该代码将添加为具有数据库值不同于用户在编辑页上输入的每一列的自定义错误消息:

if (databaseValues.Name != currentValues.Name)
ModelState.AddModelError("Name", "Current value: " + databaseValues.Name);
// ...

较长的错误消息解释发生了什么和怎样做的事情:

ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The"
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again. Otherwise click the Back to List hyperlink.");

最后,代码将Department对象的RowVersion值设置为从数据库中检索新值。这个新的 RowVersion值将存储在隐藏字段中,当重新显示编辑页,并在用户单击保存,在下一次只发生以来的编辑页面重新显示的并发错误将被捕获。

Views\Department\Edit.cshtml,添加一个隐藏的字段来保存紧接该隐藏的字段的DepartmentID属性的RowVersion属性值:

@model ContosoUniversity.Models.Department

@{
ViewBag.Title = "Edit";
} <h2>Edit</h2> @using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true) <fieldset>
<legend>Department</legend> @Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion) <div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>

Views\Department\Index.cshtml,用下面的代码向左移动行链接并更改页标题和列标题在Administrator列中显示FullName而不是LastName替换现有的代码:

@model IEnumerable<ContosoUniversity.Models.Department>

@{
ViewBag.Title = "Departments";
} <h2>Departments</h2> <p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th></th>
<th>Name</th>
<th>Budget</th>
<th>Start Date</th>
<th>Administrator</th>
</tr> @foreach (var item in Model) {
<tr>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.DepartmentID }) |
@Html.ActionLink("Details", "Details", new { id=item.DepartmentID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.DepartmentID })
</td>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Budget)
</td>
<td>
@Html.DisplayFor(modelItem => item.StartDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Administrator.FullName)
</td>
</tr>
} </table>

测试并发处理

运行该站点并单击部门:

右键单击编辑超链接为Kim Abercrombie和选择在新选项卡中打开然后单击编辑超链接为 Kim Abercrombie。这两个窗口显示相同的信息。

更改第一个浏览器窗口中的字段并单击保存.

浏览器显示更改后的值与索引页。

更改第二个浏览器窗口中的任何字段并单击保存.

单击第二次的浏览器窗口中的保存。您看到一条错误消息:

再次单击保存。你在第二个浏览器中输入的值是与您在第一次浏览器中更改的数据的原始值一起保存。Index页出现时,你看到的已保存的值。

更新删除页

对于删除页面,实体框架检测到并发冲突而引起的其他编辑新闻部以类似的方式。当HttpGet Delete方法显示确认视图时,视图包括原始 RowVersion值的隐藏字段中。该值是然后提供给用户确认删除时,将调用HttpPost Delete方法。当实体框架创建 SQLDELETE命令时,它包括一个WHERE子句与原始 RowVersion值。如果在零行命令结果的影响 (即行更名后显示删除确认页),并发异常,和HttpGet Delete方法称为一个错误标志设置为true以重新显示确认页,并显示错误消息。它也是可能的零行受到影响,因为该行已被删除由另一个用户,所以在这种情况下显示不同的错误消息。

DepartmentController.cs,用下面的代码替换HttpGet Delete方法:

public ActionResult Delete(int id, bool? concurrencyError)
{
Department department = db.Departments.Find(id); if (concurrencyError.GetValueOrDefault())
{
if (department == null)
{
ViewBag.ConcurrencyErrorMessage = "The record you attempted to delete "
+ "was deleted by another user after you got the original values. "
+ "Click the Back to List hyperlink.";
}
else
{
ViewBag.ConcurrencyErrorMessage = "The record you attempted to delete "
+ "was modified by another user after you got the original values. "
+ "The delete operation was canceled and the current values in the "
+ "database have been displayed. If you still want to delete this "
+ "record, click the Delete button again. Otherwise "
+ "click the Back to List hyperlink.";
}
} return View(department);
}

该方法接受一个可选的参数,该值指示是否在页面被重新显示后并发错误。如果此标志为true,错误消息是发送到使用ViewBag属性的视图。

HttpPost Delete方法 (称为DeleteConfirmed) 中的代码替换为以下代码:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(Department department)
{
try
{
db.Entry(department).State = EntityState.Deleted;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException)
{
return RedirectToAction("Delete", new { concurrencyError=true } );
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name after DataException and add a line here to write a log.
ModelState.AddModelError(string.Empty, "Unable to delete. Try again, and if the problem persists contact your system administrator.");
return View(department);
}
}

在您刚更换搭建代码中,这种方法接受只有一个记录 ID:

        public ActionResult DeleteConfirmed(int id)

你改变了此参数,对Department实体实例创建的模型联编程序。这使您可以访问到除了记录关键的 RowVersion属性值。

        public ActionResult Delete(Department department)

Delete,也有从DeleteConfirmed 改变操作方法的名称。搭建的代码命名为DeleteConfirmed ,给出了HttpPost方法独特的签名HttpPost Delete方法。(CLR 需要有不同的方法参数的重载的方法)。现在,签名是独一无二的你可以坚持使用 MVC 公约并使用HttpPost相同的名称,HttpGet删除方法。

如果捕获到并发错误,代码重新显示删除确认页,并提供一个标志,指示它应该显示并发错误消息。

Views\Department\Delete.cshtml,将搭建的代码替换下面的代码,使一些格式的更改并将添加到错误消息字段中。突出显示所做的更改。

@model ContosoUniversity.Models.Department

@{
ViewBag.Title = "Delete";
} <h2>Delete</h2> <p class="error">@ViewBag.ConcurrencyErrorMessage</p> <h3>Are you sure you want to delete this?</h3>
<fieldset>
<legend>Department</legend> <div class="display-label">
@Html.DisplayNameFor(model => model.Name)
</div>
<div class="display-field">
@Html.DisplayFor(model => model.Name)
</div> <div class="display-label">
@Html.DisplayNameFor(model => model.Budget)
</div>
<div class="display-field">
@Html.DisplayFor(model => model.Budget)
</div> <div class="display-label">
@Html.DisplayNameFor(model => model.StartDate)
</div>
<div class="display-field">
@Html.DisplayFor(model => model.StartDate)
</div> <div class="display-label">
@Html.DisplayNameFor(model => model.Administrator.FullName)
</div>
<div class="display-field">
@Html.DisplayFor(model => model.Administrator.FullName)
</div>
</fieldset>
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion)
<p>
<input type="submit" value="Delete" /> |
@Html.ActionLink("Back to List", "Index")
</p>
}

此代码将添加h2h3标题之间的错误消息:

<p class="error">@ViewBag.ConcurrencyErrorMessage</p>

它将LastName替换FullNameAdministrator字段:

<div class="display-label">
@Html.LabelFor(model => model.InstructorID)
</div>
<div class="display-field">
@Html.DisplayFor(model => model.Administrator.FullName)
</div>

最后,它添加隐藏的字段的DepartmentID RowVersion的属性Html.BeginForm语句之后:

@Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion)

运行部门索引页。右键单击英语系的删除超链接,然后选择在新窗口中打开第一个窗口点击编辑超链接为英语系。

在第一个窗口中,更改其中一个值,并单击保存 :

索引页反映此更改。

在第二个窗口中,单击删除.

你看到并发错误消息,并与当前数据库刷新部值。

如果再次单击删除,您正在被重定向到索引页面,显示该署已被删除。

摘要

这样就完成了处理并发冲突的简介。关于其他的方法来处理各种并发性的场景的信息,请参阅在实体框架团队博客上的乐观并发模式使用属性值。下一个教程演示如何实现每个层次结构一个表继承的InstructorStudent的实体。

这篇翻译了很久。。。。有很多概念性的东西。不知翻译的是否清楚?附上原文地址

原文地址:http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application

 

Entity Framework 处理并发的更多相关文章

  1. EntityFramework_MVC4中EF5 新手入门教程之七 ---7.通过 Entity Framework 处理并发

    在以前的两个教程你对关联数据进行了操作.本教程展示如何处理并发性.您将创建工作与各Department实体的 web 页和页,编辑和删除Department实体将处理并发错误.下面的插图显示索引和删除 ...

  2. Entity Framework 数据并发访问错误原因分析与系统架构优化

    博客地址 http://blog.csdn.net/foxdave 本文主要记录近两天针对项目发生的数据访问问题的分析研究过程与系统架构优化,我喜欢说通俗的白话,高手轻拍 1. 发现问题 系统新模块上 ...

  3. C# Entity Framework并发处理

    原网站:C# Entity Framework并发处理 在软件开发过程中,并发控制是确保及时纠正由并发操作导致的错误的一种机制.从 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NE ...

  4. C#综合揭秘——Entity Framework 并发处理详解

    引言 在软件开发过程中,并发控制是确保及时纠正由并发操作导致的错误的一种机制.从 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NET Entity Framework,.NET 都 ...

  5. 浅析Entity Framework Core中的并发处理

    前言 Entity Framework Core 2.0更新也已经有一段时间了,园子里也有不少的文章.. 本文主要是浅析一下Entity Framework Core的并发处理方式. 1.常见的并发处 ...

  6. [转]C#综合揭秘——Entity Framework 并发处理详解

    本文转自:http://www.cnblogs.com/leslies2/archive/2012/07/30/2608784.html 引言 在软件开发过程中,并发控制是确保及时纠正由并发操作导致的 ...

  7. entity framework如何控制并发

     entity framework如何控制并发 针对字段http://msdn.microsoft.com/en-us/library/vstudio/bb738618(v=vs.100).aspx ...

  8. Entity Framework Code First实现乐观并发

    Entity Framework Code First实现乐观并发 不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译自: h ...

  9. Entity Framework Core 实现MySQL 的TimeStamp/RowVersion 并发控制

    将通用的序列号生成器库 从SQL Server迁移到Mysql 遇到的一个问题,就是TimeStamp/RowVersion并发控制类型在非Microsoft SQL Server数据库中的实现.SQ ...

随机推荐

  1. unity3d c# 产生真正的随机数

    虽然能够使用Random类来生成随机数.但它是系统时钟种子,因此,有大量的反复产生伪随机数的. 您可以使用RNGCryptoServiceProvider();相对真随机数生成. 由加密服务提供程序( ...

  2. js的StringBuffer实施和使用类

    <strong>JAVA有一个StringBuffer分类,js但不是在下面,以实现自己的简单js的StringBuffer分类.</strong> //创建一个StringB ...

  3. 采取Volley,实现瀑布流

    今天停止php,在研究Volley框架的源代码,实现了瀑布流的效果. 为了实现最终的级联效应,一些需要掌握的知识: (1)自己定义布局,由于我们要监听滑究竟部的事件就要实现自己定义的ScrollVie ...

  4. NOI第一天感想&小结

    嘛...中午总算是到了深圳了--在虹桥机场和飞机上和市队大神们一起讨论各种各样奇(sang)葩(bing)的算(ren)法(lei)还是非常开心的,在此再各种膜拜一下尽管没来比赛的FFT大神@陈中瑞 ...

  5. Redis集群方案及实现

    在作出Redis群集解决方案,他跑了小半个.行表现得非常稳定在几乎相同的经历与大家分享,我写在前面的文章数据在线服务的一些探索经验,能够做为背景阅读 应用 我们的Redis集群主要承担了下面服务:1. ...

  6. php学习之道:mysql SELECT FOUND_ROWS()与COUNT(*)使用方法差别

    在mysql中 FOUND_ROWS()与COUNT(*)都能够统计记录.假设都一样为什么会有两个这种函数呢.以下我来介绍SELECT FOUND_ROWS()与COUNT(*)使用方法差别 SELE ...

  7. (大数据工程师学习路径)第二步 Vim编辑器----Vim文档编辑

    一.vim重复命令 1.重复执行上次命令 在普通模式下.(小数点)表示重复上一次的命令操作 拷贝测试文件到本地目录 $ cp /etc/protocols . 打开文件进行编辑 $ vim proto ...

  8. 写的好帮手项目官员 - Evernote 5.4(Evernote的) 中国绿色版

    Evernote (中国名:Evernote的) 是一个自由和优秀的笔记软件或个人知识管理软件.它可以帮助你有效地管理所有类型的电子票据.信息等:xbeta 我写了很多信息化管理或 Evernote ...

  9. 翻译器DIY它———算在英文文本中的单词数,字符和行数

    咳咳.这部分应该是序列化编译器DIY的,然而,在这样做DIY第一次使用前flex 为了练练手,对于后者的理解是有帮助. 在word 我经常看到一个字计数功能,因此,它是如何实现,当然,首先想到的是要经 ...

  10. AngularJS 课程

    AngularJS 教程(点我) AngularJS 通过新的属性和表达式扩展了 HTML. AngularJS 能够构建一个单一页面应用程序(SPAs:Single Page Application ...