首页 > 代码库 > C#综合揭秘——Entity Framework 并发处理详解
C#综合揭秘——Entity Framework 并发处理详解
引言
在软件开发过程中,并发控制是确保及时纠正由并发操作导致的错误的一种机制。从 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NET Entity Framework,.NET 都为并发控制提供好良好的支持方案。 并发处理方式一般分为乐观必并发与悲观必并发两种,本文将为大家介绍 Entity Framework 、 LINQ to SQL 中的并发处理方式。在本文最后,将提供一个了可参考的方案,结合事务 Transaction 处理复杂性对象的并发。
目录
一、并发处理的定义
二、模型属性的并发处理选项
三、Entity Framewrok 悲观并发
四、Entity Framework 乐观并发
五、回顾 LINQ to SQL 并发处理的方式
六、结合事务处理并发冲突
一、并发处理的定义
在软件开发过程中,当多个用户同时修改一条数据记录时,系统需要预先制定对并发的处理模式。并发处理模式主要分为两种: 第一种模式称为悲观式并发,即当一个用户已经在修改某条记录时,系统将拒绝其他用户同时修改此记录。 第二种模式称为乐观式并发,即系统允许多个用户同时修改同一条记录,系统会预先定义由数据并发所引起的并发异常处理模式,去处理修改后可能发生的冲突。常用的乐观性并发处理方法有以下几种:
- 保留最后修改的值。
- 保留最初修改的值。
- 合并多次修改的值。
相对于LINQ TO SQL 中的并发处理方式,Entity Framework 中的并发处理方式实现了不少的简化,下面为大家一一介绍。
回到目录
二、模型属性的并发处理选项
在System.Data.Metadata.Edm 命名空间中,存在ConcurencyMode 枚举,用于指定概念模型中的属性的并发选项。 ConcurencyMode 有两个成员:
成员名称 | 说明 |
None | 在写入时从不验证此属性。 这是默认的并发模式。 |
Fixed | 在写入时始终验证此属性。 |
当模型属性为默认值 None 时,系统不会对此模型属性进行检测,当同一个时间对此属性进行修改时,系统会以数据合并方式处理输入的属性值。 当模型属性为Fixed 时,系统会对此模型属性进行检测,当同一个时间对属性进行修改时,系统就会激发OptimisticConcurrencyException 异常。 开发人员可以为对象的每个属性定义不同的 ConcurencyMode 选项,选项可以在*.csdl 找看到:
1 <Schema> 2 ...... 3 ...... 4 <EntityType Name="Person"> 5 <Key> 6 <PropertyRef Name="Id" /> 7 </Key> 8 <Property Type="Int32" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="Identity" /> 9 <Property Type="String" Name="FirstName" MaxLength="50" FixedLength="false" Unicode="true" 10 ConcurrencyMode="Fixed" />11 <Property Type="String" Name="SecondName" MaxLength="50" FixedLength="false" Unicode="true" />12 <Property Type="Int32" Name="Age" />13 <Property Type="String" Name="Address" MaxLength="50" FixedLength="false" Unicode="true" />14 <Property Type="String" Name="Telephone" MaxLength="50" FixedLength="false" Unicode="true" />15 <Property Type="String" Name="EMail" MaxLength="50" FixedLength="false" Unicode="true" />16 </EntityType>17 </Schema>
回到目录
三、Entity Framework 悲观并发
在一般的开发过程中,最常用的是悲观并发处理。.NET 提供了Lock、Monitor、Interlocked 等多个锁定数据的方式,它可以保证同一个表里的对象不会同时被多个客户进行修改,避免了系统数据出现逻辑性的错误。 由于本篇文章主要介绍并发处理方式,关于锁的介绍,请参考http://www.cnblogs.com/leslies2/archive/2012/02/08/2320914.html#t8
1 private static object o=new object(); 2 3 public int Update(Person person) 4 { 5 int n = -1; 6 try 7 { 8 lock (o) 9 {10 using (BusinessEntities context = new BusinessEntities())11 {12 var obj = context.Person.Where(x => x.Id == person.Id).First();13 if (obj != null)14 context.ApplyCurrentValues("Person", person);15 n = context.SaveChanges();16 }17 }18 }19 catch (Exception ex)20 { ...... }21 return n;22 }
使用悲观并发虽然能有效避免数据发生逻辑性的错误,但使用 lock 等方式锁定 Update 方法的操作,在用户同时更新同一数据表的数据,操作就会被延时或禁止。在千万级 PV 的大型网络系统当中使用悲观并发,有可能降低了系统的效率,此时可以考虑使用乐观并发处理。
回到目录
四、Entity Framework 乐观并发
为了解决悲观并发所带来的问题,ADO.NET Entity Framework 提供了更为高效的乐观并发处理方式。相对于LINT to SQL , ADO.NET Entity Framework 简化了乐观并发的处理方式,它可以灵活使用合并数据、保留初次输入数据、保留最新输入数据等方式处理并发冲突。
4.1 以合并方式处理并发数据
当模型属性的 ConcurencyMode 为默认值 None ,一旦同一个对象属性同时被修改,系统将以合并数据的方式处理并发冲突,这也是 Entity Framework 处理并发冲突的默认方式。合并处理方式如下:当同一时间针对同一个对象属性作出修改,系统将保存最新输入的属性值。当同一时间对同一对象的不同属性作出修改,系统将保存已被修改的属性值。下面用两个例子作出说明:
4.1.1 同时更新数据
在系统输入下面代码,获取数据库中的Person:Id 为24,FirstName为Leslie, SecondName为Lee。然后使用异步方法分两次调用Update方法,同时更新Person对象的相关属性,第一次更新把对象的 FirstName 属性改Rose,第二次更新把对象的 SecondName改为Wang,Age改为32。在使用SaveChanges保存更新时,第一个方法已经把ObjectContext1中的FirstName修改为Rose,但在第二个方法中的ObjectContext2中的FirstName依然是Leslie,此时由于三个属性的ConcurencyMode都为默认值None,系统会忽略当中的冲突,而接受所有的更新修改。
1 public class PersonDAL 2 { 3 public Person GetPerson(int id) 4 { 5 using (BusinessEntities context = new BusinessEntities()) 6 { 7 IQueryable<Person> list=context.Person.Where(x => x.Id == id); 8 return list.First(); 9 }10 }11 12 public void Update(Person person)13 {14 using (BusinessEntities context = new BusinessEntities())15 {16 //显示输入新数据的信息17 Display("Current", person);18 var obj = context.Person.Where(x => x.Id == person.Id).First();19 if (obj != null)20 context.ApplyCurrentValues("Person", person);21 22 //虚拟操作,保证数据能同时加入到上下文当中23 Thread.Sleep(100);24 context.SaveChanges();25 }26 }27 28 delegate void MyDelegate(Person person);29 30 public static void Main(string[] args)31 {32 //在更新数据前显示对象信息33 PersonDAL personDAL = new PersonDAL();34 var beforeObj = personDAL.GetPerson(24);35 personDAL.Display("Before", beforeObj);36 37 //更新Person的SecondName,Age两个属性38 Person person1 = new Person();39 person1.Id = 24;40 person1.FirstName = "Leslie";41 person1.SecondName = "Wang";42 person1.Age = 32;43 person1.Address = "Tianhe";44 person1.Telephone = "13660123456";45 person1.EMail = "Leslie@163.com";46 47 //更新Person的FirstName属性48 Person person2 = new Person();49 person2.Id = 24;50 person2.FirstName = "Rose";51 person2.SecondName = "Lee";52 person2.Age = 34;53 person2.Address = "Tianhe";54 person2.Telephone = "13660123456";55 person2.EMail = "Leslie@163.com";56 57 //使用异步方式同时更新数据58 MyDelegate myDelegate = new MyDelegate(personDAL.Update);59 myDelegate.BeginInvoke(person1, null, null);60 myDelegate.BeginInvoke(person2, null, null);61 62 Thread.Sleep(300);63 //在更新数据后显示对象信息64 var afterObj = personDAL.GetPerson(24);65 personDAL.Display("After", afterObj);66 Console.ReadKey();67 }68 69 public void Display(string message,Person person)70 { 71 String data = http://www.mamicode.com/string.Format("{0}\n Person Message:\n Id:{1} FirstName:{2} "+72 "SecondName:{3} Age:{4}\n Address:{5} Telephone:{6} EMail:{7}\n",73 message, person.Id, person.FirstName, person.SecondName, person.Age, 74 person.Address, person.Telephone, person.EMail);75 Console.WriteLine(data);76 }77 }
根据操作结果可以看到,在Entity Framework的默认环境情况下,系统会使用合并方式处理并发,把输入数据的所有修改值都保存到当前上下文当中,并同时修改数据库当中的值。
4.1.2 删除与更新操作同时运行
Entity Framework 能以完善的机制灵活处理同时更新同一对象的操作,但一旦删除操作与更新操作同时运行时,就可能存在逻辑性的异常。例如:两个客户端同时加载了同一个对象,第一个客户端更新了数据后,把数据再次提交。但在提交前,第二个客户端已经把数据库中的已有数据删除。此时,上下文中的对象处于不同的状态底下,将会引发 OptimisticConcurrencyException 异常。 遇到此异常时,可以用 try(OptimisticConcurrencyException){...} catch {...} 方式捕获异常,然后使用 ObjectStateManager.ChangeObjectState 方法更改对象的 EntityState 属性。把EntityState 更改为 Added ,被删除的数据便会被再次加载。若把 EntityState 更改为 Detached 时,数据便会被顺利删除。下面把对象的 EntityState 属性更改为 Added 作为例子。
1 public class PersonDAL 2 { 3 delegate int MyDelegate(Person person); 4 5 public static void Main(string[] args) 6 { 7 //在更新数据前显示对象信息 8 PersonDAL personDAL = new PersonDAL(); 9 var beforeObj = personDAL.GetPerson(51); 10 personDAL.DisplayProperty("Begin", beforeObj); 11 12 //更新Person的属性 13 Person person1 = new Person(); 14 person1.Id = 51; 15 person1.FirstName = "Leslie"; 16 person1.SecondName = "Wang"; 17 person1.Age = 32; 18 person1.Address = "Tianhe"; 19 person1.Telephone = "13660123456"; 20 person1.EMail = "Leslie@163.com"; 21 22 //使用异步方式更新数据 23 MyDelegate myDelegate = new MyDelegate(personDAL.Update); 24 IAsyncResult reslut=myDelegate.BeginInvoke(person1, null, null); 25 26 //同步删除原有数据 27 personDAL.Delete(51); 28 //显示删除后重新被加载的数据 29 var afterObj = personDAL.GetPerson(myDelegate.EndInvoke(reslut)); 30 personDAL.DisplayProperty("End", afterObj); 31 } 32 33 public Person GetPerson(int id) 34 { 35 using (BusinessEntities context = new BusinessEntities()) 36 { 37 IQueryable<Person> list=context.Person.Where(x => x.Id == id); 38 return list.First(); 39 } 40 } 41 42 //更新对象 43 public int Update(Person person) 44 { 45 int returnValue=http://www.mamicode.com/-1; 46 using (BusinessEntities context = new BusinessEntities()) 47 { 48 var obj = context.Person.Where(x => x.Id == person.Id).First(); 49 //显示对象所处状态 50 DisplayState("Before Update", obj); 51 try 52 { 53 if (obj != null) 54 context.ApplyCurrentValues("Person", person); 55 //虚拟操作,保证数据已经在数据库中被异步删除 56 Thread.Sleep(100); 57 context.SaveChanges(); 58 returnValue =http://www.mamicode.com/ obj.Id; 59 } 60 catch (System.Data.OptimisticConcurrencyException ex) 61 { 62 //把对象的状态更改为 Added 63 context.ObjectStateManager.ChangeObjectState(obj, System.Data.EntityState.Added); 64 context.SaveChanges(); 65 returnValue=http://www.mamicode.com/obj.Id; 66 } 67 } 68 return returnValue; 69 } 70 71 //删除对象 72 public void Delete(int id) 73 { 74 using (BusinessEntities context = new BusinessEntities()) 75 { 76 var person1 = context.Person.Where(x => x.Id == id).First(); 77 if (person1 != null) 78 context.Person.DeleteObject(person1); 79 context.SaveChanges(); 80 //显示对象现在所处的状态 81 DisplayState("After Delete:", person1); 82 } 83 } 84 85 //