在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
本文转自:http://www.cnblogs.com/leslies2/archive/2012/07/30/2608784.html 引言 在软件开发过程中,并发控制是确保及时纠正由并发操作导致的错误的一种机制。从 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NET Entity Framework,.NET 都为并发控制提供好良好的支持方案。
目录
一、并发处理的定义 在软件开发过程中,当多个用户同时修改一条数据记录时,系统需要预先制定对并发的处理模式。并发处理模式主要分为两种:
相对于LINQ TO SQL 中的并发处理方式,Entity Framework 中的并发处理方式实现了不少的简化,下面为大家一一介绍。 二、模型属性的并发处理选项 在System.Data.Metadata.Edm 命名空间中,存在ConcurencyMode 枚举,用于指定概念模型中的属性的并发选项。
当模型属性为默认值 None 时,系统不会对此模型属性进行检测,当同一个时间对此属性进行修改时,系统会以数据合并方式处理输入的属性值。 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 等多个锁定数据的方式,它可以保证同一个表里的对象不会同时被多个客户进行修改,避免了系统数据出现逻辑性的错误。 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 = "[email protected]"; 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 = "[email protected]"; 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 = 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 异常。 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 = "[email protected]"; 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=-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 = 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=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 //显示对象现在所处的状态 86 public void DisplayState(string message,Person person) 87 { 88 String data = string.Format("{0}\n Person State:{1}\n", 89 message,person.EntityState); 90 Console.WriteLine(data); 91 } 92 //显示对象相关属性 93 public void DisplayProperty(string message, Person person) 94 { 95 String data = string.Format("{0}\n Person Message:\n Id:{1} FirstName:{2} " + 96 "SecondName:{3} Age:{4}\n Address:{5} Telephone:{6} EMail:{7}\n", 97 message, person.Id, person.FirstName, person.SecondName, person.Age, 98 person.Address, person.Telephone, person.EMail); 99 Console.WriteLine(data); 100 } 101 } 观察运行测试结果,当运行 Delete 方法,对象已经在数据库中被删除,对象的EntityState处于 Detached 状态。此时使用 SaveChanges 保存更新数据时,引发了OptimisticConcurrencyException 异常。在捕获异常,把对象状态更改为 Added ,再使用SaveChanges保存数据,数据就能顺利地保存到数据库中。但值得留意,因为对象是在删除后重新加载的,所以对象的 Id 也会被同步更新。 以合并数据的方式处理并发冲突固然方便快节,但在业务逻辑较为复杂的系统下并不适合使用此处理方式。比如在常见的Order、OrderItem的表格中,OrderItem 的单价,数量会直接影响Order的总体价格,这样使用合并数据的方式处理并发,有可能引起逻辑性的错误。此时,应该考虑以其他方式处理并发冲突。
4.2 当发生数据并发时,保留最新输入的数据 要验证输入对象的属性,必须先把该属性的 ConcurencyMode 设置为 Fixed,这样系统就会实时检测对象属性的输入值 。 在下面的例子当,系统启动前先把 Person 的 FirstName、SecondName 两个属性的 ConcurencyMode 属性设置为Fixed,使系统能监视这两个属性的更改。所输入的数据只在FirstName、SecondName 两个值中作出修改。在数据提交前先以 DisplayProperty 方法显示数据库最初的数据属性,在数据初次更新后再次调用 DisplayProperty 显示更新后的数据属性。在第二次更新数据时,由调用ObjectContext.SaveChanges时,数据库中的数据已经被修改,与当前上下文ObjectContext 的数据存在冲突,系统将激发 OptimisticConcurrencyException 异常,此时把引发异常的对象属性再次显示出来。对异常进行处理后,显示数据库中最终的对象值。 1 public class PersonDAL 2 { 3 delegate void MyDelegate(Person person); 4 5 public static void Main(string[] args) 6 { 7 //在更新数据前显示对象信息 8 PersonDAL personDAL = new PersonDAL(); 9 var beforeObj = personDAL.GetPerson(52); 10 personDAL.DisplayProperty("Before", beforeObj); 11 12 //更新Person的FirstName、SecondName属性 13 Person person1 = new Person(); 14 person1.Id = 52; 15 person1.FirstName = "Mike"; 16 person1.SecondName = "Wang"; 17 person1.Age = 32; 18 person1.Address = "Tianhe"; 19 person1.Telephone = "13660123456"; 20 person1.EMail = "[email protected]"; 21 22 //更新Person的FirstName、SecondName属性 23 Person person2 = new Person(); 24 person2.Id = 52; 25 person2.FirstName = "Rose"; 26 person2.SecondName = "Chen"; 27 person2.Age = 32; 28 person2.Address = "Tianhe"; 29 person2.Telephone = "13660123456"; 30 person2.EMail = "[email protected]"; 31 32 //使用异步方式更新数据 33 MyDelegate myDelegate = new MyDelegate(personDAL.Update); 34 myDelegate.BeginInvoke(person1, null, null); 35 myDelegate.BeginInvoke(person2, null, null); 36 //显示完成更新后数据源中的对应属性 37 Thread.Sleep(1000); 38 var afterObj = personDAL.GetPerson(52); 39 personDAL.DisplayProperty("After", afterObj); 40 } 41 42 public Person GetPerson(int id) 43 { 44 using (BusinessEntities context = new BusinessEntities()) 45 { 46 IQueryable<Person> list=context.Person.Where(x => x.Id == id); 47 return list.First(); 48 } 49 } 50 51 //更新对象 52 public void Update(Person person) 53 { 54 using (BusinessEntities context = new BusinessEntities()) 55 { 56 var obj = context.Person.Where(x => x.Id == person.Id).First(); 57 try 58 { 59 if (obj!=null) 60 context.ApplyCurrentValues("Person", person); 61 //虚拟操作,保证数据被同步加载 62 Thread.Sleep(100); 63 context.SaveChanges(); 64 //显示第一次更新后的数据属性 65 this.DisplayProperty("Current", person); 66 } 67 catch (System.Data.OptimisticConcurrencyException ex) 68 { 69 //显示发生OptimisticConcurrencyException异常所输入的数据属性 70 this.DisplayProperty("OptimisticConcurrencyException", person); 71 72 if (person.EntityKey == null) 73 person.EntityKey = new System.Data.EntityKey("BusinessEntities.Person", 74 "Id", person.Id); 75 //保持上下文当中对象的现有属性 76 context.Refresh(RefreshMode.ClientWins, person); 77 context.SaveChanges(); 78 } 79 } 80 } 81 82 //显示对象相关属性 83 public void DisplayProperty(string message, Person person) 84 { 85 String data = string.Format("{0}\n Person Message:\n Id:{1} FirstName:{2} " + 86 "SecondName:{3} Age:{4}\n Address:{5} Telephone:{6} EMail:{7}\n", 87 message, person.Id, person.FirstName, person.SecondName, person.Age, 88 person.Address, person.Telephone, person.EMail); 89 Console.WriteLine(data); 90 } 91 } 观察测试结果,可见当RefreshMode状态为ClientWins时,系统将会保存上下文当中的对象属性,使用此方法可以在发生并发异常时保持最新输入的对象属性。
4.3 当发生数据并发时,保留最初输入的数据 把对象属性的 ConcurencyMode 设置为 Fixed 后,同时更新该属性,将会激发 OptimisticConcurrencyException 异常。此时使用 ObjectContext.Refresh (RefreshMode,object) 刷新上下文中该对象的状态,当 RefreshMode 为 StoreWins 时,系统就会把数据源中的数据代替上下文中的数据。 1 public class PersonDAL 2 { 3 delegate void MyDelegate(Person person); 4 5 public static void Main(string[] args) 6 { 7 //在更新数据前显示对象信息 8 PersonDAL personDAL = new PersonDAL(); 9 var beforeObj = personDAL.GetPerson(52); 10 personDAL.DisplayProperty("Before", beforeObj); 11 12 //更新Person的FirstName、SecondName属性 13 Person person1 = new Person(); 14 person1.Id = 52; 15 person1.FirstName = "Mike"; 16 person1.SecondName = "Wang"; 17 person1.Age = 32; 18 person1.Address = "Tianhe"; 19 person1.Telephone = "13660123456"; 20 person1.EMail = "[email protected]"; 21 22 //更新Person的FirstName、SecondName属性 23 Person person2 = new Person(); 24 person2.Id = 52; 25 person2.FirstName = "Rose"; 26 person2.SecondName = "Chen"; 27 person2.Age = 32; 28 person2.Address = "Tianhe"; 29 person2.Telephone = "13660123456"; 30 person2.EMail = "[email protected]"; 31 32 //使用异步方式更新数据 33 MyDelegate myDelegate = new MyDelegate(personDAL.Update); 34 myDelegate.BeginInvoke(person1, null, null); 35 myDelegate.BeginInvoke(person2, null, null); 36 //显示完成更新后数据源中的对应属性 37 Thread.Sleep(1000); 38 var afterObj = personDAL.GetPerson(52); 39 personDAL.DisplayProperty("After", afterObj); 40 } 41 42 public Person GetPerson(int id) 43 { 44 using (BusinessEntities context = new BusinessEntities()) 45 { 46 IQueryable<Person> list=context.Person.Where(x => x.Id == id); 47 return list.First(); 48 } 49 } 50 51 //更新对象 52 public void Update(Person person) 53 { 54 using (BusinessEntities context = new BusinessEntities()) 55 { 56 var obj = context.Person.Where(x => x.Id == person.Id).First(); 57 try 58 { 59 if (obj!=null) 60 context.ApplyCurrentValues("Person", person); 61 //虚拟操作,保证数据被同步加载 62 Thread.Sleep(100); 63 context.SaveChanges(); 64 //显示第一次更新后的数据属性 65 this.DisplayProperty("Current", person); 66 } 67 catch (System.Data.OptimisticConcurrencyException ex) 68 { 69 //显示发生OptimisticConcurrencyException异常所输入的数据属性 70 this.DisplayProperty("OptimisticConcurrencyException", person); 71 72 if (person.EntityKey == null) 73 person.EntityKey = new System.Data.EntityKey("BusinessEntities.Person", 74 "Id", person.Id); 75 //保持数据源中对象的现有属性 76 context.Refresh(RefreshMode.StoreWins, person); 77 context.SaveChanges(); 78 } 79 } 80 } 81 82 //显示对象相关属性 83 public void DisplayProperty(string message, Person person) 84 { 85 String data = string.Format("{0}\n Person Message:\n Id:{1} FirstName:{2} " + 86 "SecondName:{3} Age:{4}\n Address:{5} Telephone:{6} EMail:{7}\n", 87 message, person.Id, person.FirstName, person.SecondName, person.Age, 88 person.Address, person.Telephone, person.EMail); 89 Console.WriteLine(data); 90 } 91 } 观察测试结果,可见当 RefreshMode 状态为 StoreWins 时,系统将会以数据源中的数据代替上下文当中的对象属性。在业务逻辑较为复杂的的系统当中,建议使用此方式处理并发异常。
五、回顾 LINQ to SQL 并发处理的方式 Entity Framework 当中简化了并发处理的方式,然而温故而知新,LINQ to SQL 中并发处理所使用的方式也值得回顾一下。下面将与大家一起回顾一下 LINQ to SQL 当中并发处理的方式。
DataContext.SubmitChanges(ConflictMode) SubmitChanges 尝试运行对数据库的所有更新。
ConfilctMode成员图
ResolveAll 能。
RefreshMode 成员图
当 Person 表格的多个列的 UpdateCheck 属性都为默认值 Always 时,多个客户同时更新此数据表,最后使用 DataContext.SubmitChanges(ConflictMode.ContinuOnConflict) 同时提交数据时,系统就会释放出 ChangeConflictException 异常。系统可以以捕获此并发异常后,再决定采取 KeepChanges、KeepCurrentValues、OverwriteCurrentValues 等方式处理数据。 1 public class PersonDAL 2 { 3 delegate void MyDelegate(Person person); 4 5 static void Main(string[] args) 6 { 7 //在更新数据前显示对象信息 8 PersonDAL personDAL = new PersonDAL(); 9 var beforeObj = personDAL.GetPerson(52); 10 personDAL.DisplayProperty("Before", beforeObj); 11 12 //更新Person的FirstName、SecondName属性 13 Person person1 = new Person(); 14 person1.Id = 52; 15 person1.FirstName = "Mike"; 16 person1.SecondName = "Wang"; 17 person1.Age = 32; 18 person1.Address = "Tianhe"; 19 person1.Telephone = "13660123456"; 20 person1.EMail = "[email protected]"; 21 22 //更新Person的FirstName、SecondName属性 23 Person person2 = new Person(); 24 person2.Id = 52; 25 person2.FirstName = "Rose"; 26 person2.SecondName = "Chen"; 27 person2.Age = 32; 28 person2.Address = "Tianhe"; 29 person2.Telephone = "13660123456"; 30 person2.EMail = "[email protected]"; 31 32 //使用异步方式更新数据 33 MyDelegate myDelegate = new MyDelegate(personDAL.Update); 34 myDelegate.BeginInvoke(person1, null, null); 35 myDelegate.BeginInvoke(person2, null, null); 36 37 //显示更新后的对象信息 38 Thread.Sleep(1000); 39 var afterObj = personDAL.GetPerson(52); 40 personDAL.DisplayProperty("After", afterObj); 41 Console.ReadKey(); 42 } 43 44 public void Update(Person person) 45 { 46 using (BusinessDataContext context = new BusinessDataContext()) 47 { 48 try 49 { 50 var person1 = context.Person.Where(x => x.Id == person.Id).First(); 51 if (person1 != null) 52 { 53 person1.Address = person.Address; 54 person1.Age = person.Age; 55 person1.EMail = person.EMail; 56 person1.FirstName = person.FirstName; 57 person1.SecondName = person.SecondName; 58 person1.Telephone = person.Telephone; 59 } 60 //虚拟操作,保证多个值同时提交 61 Thread.Sleep(100); 62 context.SubmitChanges(ConflictMode.ContinueOnConflict); 63 DisplayProperty("SubmitChanges Success",person); 64 } 65 catch (ChangeConflictException ex) 66 { 67 //保持最新输入的上下文数据 68 DisplayProperty("ChangeConflictException", person); 69 context.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges); 70 context.SubmitChanges(); 71 } 72 } 73 } 74 75 public Person GetPerson(int id) 76 { 77 using (BusinessDataContext context = new BusinessDataContext()) 78 { 79 var person = context.Person.Where(x => x.Id == id); 80 return person.First(); 81 } 82 } 83 84 //显示对象相关属性 85 public void DisplayProperty(string message, Person person) 86 { 87 String data = string.Format("{0}\n Person Message:\n Id:{1} FirstName:{2} " + 88 "SecondName:{3} Age:{4}\n Address:{5} Telephone:{6} EMail:{7}\n", 89 message, person.Id, person.FirstName, person.SecondName, person.Age, 90 person.Address, person.Telephone, person.EMail); 91 Console.WriteLine(data); 92 } 93 } 例子当中使用 RefreshMode.KeepChanges 的方式处理并发,系统会保存最新输入的数据。观察测试结果,当系统发生第一次更新时,数据成功保存到数据库当中。在 DataContext 未释放前,再次输入数据,引发了ChangeConflictException异常。在捕获此并发异常后,系统以 RefreshMode.KeepChanges 方式进行处理,最后新输入的数据成功保存到数据库当中。
六、结合事务处理并发冲突 Entity Framework 中已经有比较完善的机制处理并发,但使用乐观性并发处理数据,一旦多个客户端同时更新同一张表格的同一个对象时,将会激发 OptimisticConcurrencyException 异常,系统必须预先定制好处理方案对此并发异常进行处理。结合事务处理并发异常,是一个比较高效的数据管理方式。事务能对数据的更新进行检测,一旦发现异常,便会实现回滚。本文会使用常用的隐式事务 TransactionScope 作为例子进行介绍,对事务的详细介绍,可以参考 “C#综合揭秘——细说事务”。其实在CodePlex网站可以看到,微软在ObjectContext.Savechanges方法的实现中,已经使用了事务用于保证数据输入的正确性。但对于一部分多表操作的方法中,依然需要使用事务在多表更新时保证一致性。 1 public class OrderDAL 2 { 3 public void UpdateOrder(Order order) 4 { 5 using (BusinessEntities context = new BusinessEntities()) 6 { 7 Order objResult = context.Order.Include("OrderItem") 8 .Where(x => x.Id == order.Id).First(); 9 10 if (objResult != null) 11 { 12 try 13 { 14 //把原有相关的OrderItem对象全部删除 15 foreach (var item in objResult.OrderItem.ToList()) 16 context.OrderItem.DeleteObject(item); 17 //更新 Order对象的属性,加入对应新的OrderItem对象 18 context.ApplyCurrentValues("BusinessEntities.Order", order); 19 foreach (var item in order.OrderItem.ToList()) 20 { 21 //先把OrderItem的导航属性Order变为空值 22 //否则系统会显示重复插入的异常 23 item.Order = null; 24 context.OrderItem.AddObject(item); 25 } 26 context.SaveChanges(); 27 } 28 catch (System.Data.OptimisticConcurrencyException ex) 29 {......} 30 catch (Exception ex) 31 {......} 32 } 33 } 34 } 35 } 36 37 public class PersonDAL 38 { 39 //更新Person对象 40 public void Update(Person person) 41 { 42 using (BusinessEntities context = new BusinessEntities()) 43 { 44 var obj = context.Person.Where(x => x.Id == person.Id).First(); 45 if (obj != null) 46 context.ApplyCurrentValues("Person", person); 47 context.SaveChanges(); 48 } 49 } 50 } 51 52 public class UpdateOrder 53 { 54 private OrderDAL orderDAL = new OrderDAL(); 55 private PersonDAL personDAL = new PersonDAL(); 56 57 public void DoWork(Person person, Order order) 58 { 59 lock(this) 60 { 61 //操作时将同时更新Order、OrderItem、Person三个表格 62 //为了避免其中一个操作出现错误而引起数据逻辑性错误 63 //应使用事务作为保护,在出现错误时实现同步回滚 64 using (TransactionScope scope = new TransactionScope()) 65 { 66 orderDAL.UpdateOrder(order); 67 personDAL.Update(person); 68 scope.Complete(); 69 } 70 } 71 } 72 } 在复杂的多表数据处理过程中,推荐使用事务结合锁来进行处理。 总结 并发话题与线程、进程等其他话题有所不同,它并没有复杂的类和方法。处理并发来来去去都是简单的几行代码,它所重视的是并发异常发生后所带来的后果与处理方式。与中国传统的太极相似,并发只重其意,不重其招,只要深入地了解其过程,考虑其可能带来的问题后,你便可以对其收发自如。
C#综合揭秘 通过修改注册表建立Windows自定义协议 作者:风尘浪子 原创作品,转载时请注明作者及出处 C#综合揭秘
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论