Entity Framework Code First实现乐观并发
不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址
本博文翻译自:
https://www.codeproject.com/Articles/817432/Optimistic-Concurrency-in-Entity-Framework-Code-Fi
本文描述了使用Entity Framework Code First处理乐观并发性的不同配置
并发性在计算机科学中,并发性是系统的一个属性,在这个系统中,多个计算同时执行,并且有可能相互影响。
在web应用程序中,这是一个多用户环境,在保存数据库中的数据时,可能存在并发性。并发性大致分为两种类型:1)悲观并发2)乐观并发
1) 悲观并发数据库中的悲观并发包括锁行,以防止其他用户以影响当前用户的方式修改数据。
在这种方法中,用户执行一个操作,其中一个锁被应用,其他用户不能在该记录上执行相同的操作,直到该锁被释放。
2) 乐观并发相比之下,在乐观并发中,当用户阅读时,行不会被锁定。当用户试图更新这一行时,系统必须确定该记录是否被另一个用户修改过,因为它被读取了。
开始编写代码让我们创建一个控制台应用程序来探索处理乐观并发的不同情况。
步骤下表显示了用于乐观并发的不同配置。
配置乐观并发
Convention None
Data Annotation [Timestamp]
Fluent API .IsRowVersion()
1) ConventionEntity Framework Code First 没有任何处理乐观并发的约定。您可以使用Data Annotation或Fluent API来处理乐观并发。
2) Data AnnotationCode First使用[Timestamp] 处理乐观并发性的属性。
a) 修改 EducationContext.cs 文件如下:
using System.Data.Entity; namespace ConcurrencyCheck.Models { class EducationContext : DbContext { public EducationContext() : base("EducationContext") { } public DbSet<Student> Students { get; set; } } }在base("EducationContext")中通过Code First 指令在 App.config 文件中使用名为"EducationContext"的连接字符串
b) 修改 Student.cs 文件如下:
using System.ComponentModel.DataAnnotations; namespace ConcurrencyCheck.Models { public class Student { public int StudentId { get; set; } public string RollNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [Timestamp] public byte[] RowVersion { get; set; } } }请注意,在 Student 类中有一个属性 RowVersion ,它是 byte[] 类型,并被分配使用 [Timestamp] 属性来处理乐观的并发性。
c) 将 App.config 文件中的连接字符串更改为指向一个有效的数据库:
<connectionStrings> <add name="EducationContext" providerName="System.Data.SqlClient" connectionString="Server=DUKHABANDHU-PC; Database=ConcurrencyCheck;Integrated Security=SSPI" /> </connectionStrings>在这里,我们将数据库名称作为ConcurrencyCheck,它将在应用程序运行时通过 Code First 创建。
d) 修改 Program.cs 文件在每次应用程序运行时都要删除和创建数据库:
static void Main(string[] args) { Database.SetInitializer(new DropCreateDatabaseAlways<EducationContext>()); using (var context = new EducationContext()) { context.Students.Add(new Student { FirstName = "Dukhabandhu", LastName = "Sahoo", RollNumber = "1" }); context.SaveChanges(); } Console.WriteLine("Database Created!!!"); Console.ReadKey(); }如果运行该应用程序,代码首先将创建数据库 ConcurrenCheck 它拥有两个表 MigrationHistory 和 Students 。
如果您看到 Students 表中的 RowVersion 列(在SQL Sever中),它的数据类型是 timestamp 。
RowVersion 和 TimeStamp 是不同数据库提供程序用于相同目的的两个术语。当创建或更新 Students 表中的记录时,数据库将自动更新 RowVersion 值到新值。即使您为 rowversion 列发送值,数据库(SQL Server)也不使用该值来进行插入或更新操作。
当添加到 Students 表的新记录时生成的SQL:
exec sp_executesql N'INSERT [dbo].[Students]([RollNumber], [FirstName], [LastName]) VALUES (@0, @1, @2) SELECT [StudentId], [RowVersion] FROM [dbo].[Students] WHERE @@ROWCOUNT > 0 AND [StudentId] = scope_identity()',N'@0 nvarchar(max) ,@1 nvarchar(max) ,@2 nvarchar(max) ',@0=N'1',@1=N'Dukhabandhu',@2=N'Sahoo'您可以看到查询不仅插入了一个新记录,而且还返回了 RowVersion 的值。
当进行更新和删除操作时,会发生实际的并发检查。在更新和删除 Students 表的记录时,请参阅下面如何发生并发检查。
UPDATE SQL exec sp_executesql N'UPDATE [dbo].[Students] SET [RollNumber] = @0 WHERE (([StudentId] = @1) AND ([RowVersion] = @2)) SELECT [RowVersion] FROM [dbo].[Students] WHERE @@ROWCOUNT > 0 AND [StudentId] = @1',N'@0 nvarchar(max) ,@1 int,@2 binary(8)',@0=N'2',@1=1,@2=0x00000000000007D1我们看到 WHERE 条件,在更新记录时,它比较了 studentid (主键)和 RowVersion 。
DELETE SQL exec sp_executesql N'DELETE [dbo].[Students] WHERE (([StudentId] = @0) AND ([RowVersion] = @1))',N'@0 int,@1 binary(8)',@0=1,@1=0x00000000000007D1在删除记录代码之前,先创建一个查询来比较标识符(主键 StudentId )和行版本( RowVersion 字段)用于乐观并发。
3) Fluent APIFluent API使用 IsRowVersion() 方法来配置乐观并发。
为了测试Fluent API的配置,从 Students 类的 RowVersion 属性中删除 [Timestamp] 属性,并在 EducationContext 类中覆盖 ** onmodel()** 方法:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Student>().Property(s => s.RowVersion).IsRowVersion(); base.OnModelCreating(modelBuilder); } 配置非时间戳字段