本文介绍如何处理多个用户并发更新同一实体(同时)时出现的冲突 。
web
主要是两种:一种,检查属性并发冲突,使用 [ConcurrencyCheck] ;另外一种,检测行的并发冲突,使用 rowversion 跟踪属性,若是在保存以前有修改,就报错数据库
1.用户导航到实体编辑页面;浏览器
2.第一个用户的更改还未写入数据库以前,另外一个用户更新同一实体;并发
此时,若是未启用并发检测,当发生更新时:visual-studio
最后一个更新优先。即最后一个更新的值保存到数据库。而第一个保存的值将丢失。性能
1. Jane 访问院系编辑页面,将英语系的预算从 350,000.00 美圆更改为 0.00 美圆 (第一个用户把金额改成0)学习
,ui
2.在 Jane 单击“保存”以前,John 访问了相同页面,并将开始日期字段从 2007/1/9 更改为 2013/1/9。 (在第一个用户保存以前,第二个用户把时间从07年改成13年,注意此时第二个用户看到的金额还不是0)编码
3.Jane 先单击“保存”,并在浏览器显示索引页时看到她的更改。 (第一个用户先保存,而且能够在浏览器看到他的修改,金额变0,时间不变)spa
4.John 单击“编辑”页面上的“保存”,但页面的预算仍显示为 350,000.00 美圆。 (第二个用户保存,此时的页面的预算显示未350000美圆,时间为13年)
其实这个结果取决于并发冲突的处理方式
首先声明,这是一个乐观并发冲突,那么什么是乐观并发冲突呢?
乐观并发冲突容许发生并发冲突,并在并发冲突发生时做出正确的反映。
1. 能够跟踪用户已修改的属性,并只更新数据库中相应的列。
这样,当两个用户更新了不一样的属性,下次查看时,都将生效。
可是,这种方法,也有一些问题:
体如今例子中,就是若是下次有人浏览英语系时,将看到 Jane 和 John
两我的的更改。
2.客户端优先
即客户端的值优先于数据库存储的值。而且若是不对并发处理进行任何编码,将自动进行客户端优先
即John 的更改覆盖 Jane 的更改 。也就是说,下次有人浏览英语系时,将看到 2013/9/1 和提取的值 350,000.00 美圆
3.存储优先
这种方式能够阻止在数据库中John的更改。而且能够
当属性配置为并发令牌时:
数据库和数据模型必须配置为支持引发 DbUpdateConcurrencyException 。
检测属性的并发冲突
可以使用 ConcurrencyCheck 特性在属性级别检测并发冲突。 该特性可应用于模型上的多个属性 。[ConcurrencyCheck] 特性
检测行的并发冲突
要检测并发冲突,请将 rowversion 跟踪列添加到模型。
注意:rowversion ,
1.它是 SQL Server 特定的。 其余数据库可能没法提供类似功能。
2.用于肯定从数据库提取实体后未更改实体。
数据库生成rowversion序号,该数字随着每次行的更新递增。
在 update 或 delete 命令中,where 子句中包括 rowversion提取值 的判断 。
若是要更新的行已经修改,则 rowversion提取值与如今数据库中rowversion的值不匹配;
update 或 delete 命令不能找到行。引起一个 DbUpdateConcurrencyException 异常
例子
向 Department 实体添加跟踪属性
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace ContosoUniversity.Models { 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)] [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] [Display(Name = "Start Date")] public DateTime StartDate { get; set; } public int? InstructorID { get; set; }
[Timestamp] public byte[] RowVersion { get; set; } //跟踪属性
public Instructor Administrator { get; set; } public ICollection<Course> Courses { get; set; } } }
Timestamp 特性 指定此列包含在 update 和 delete 命令的 where 子句中。
也能够用 Fluent API 指定跟踪属性:
modelBuilder.Entity<Department>() .Property<byte[]>("RowVersion") .IsRowVersion();
如下代码显示更新 Department 名称时由 EF Core 生成的部分 T-SQL:
SET NOCOUNT ON;
UPDATE [Department] SET [Name] = @p0 WHERE [DepartmentID] = @p1 AND [RowVersion] = @p2;
SELECT [RowVersion] FROM [Department] WHERE @@ROWCOUNT = 1 AND [DepartmentID] = @p1;
前面的代码显示包含 RowVersion 的 WHERE 子句。 若是数据库 RowVersion 不等于 RowVersion 参数( @p2
),则不更新行。
@@ROWCOUNT 返回受上一语句影响的行数。 在没有行更新的状况下,EF Core 引发
DbUpdateConcurrencyException
此文主要是为了方便本身记录学习,若有错误,欢迎指正
这里附上参考资料:
https://docs.microsoft.com/en-us/aspnet/core/data/ef-rp/concurrency?view=aspnetcore-2.2&tabs=visual-studio