C# Entity Framework併發處理

来源:http://www.cnblogs.com/lvjinliang/archive/2016/11/23/6094969.html
-Advertisement-
Play Games

原網站:C# Entity Framework併發處理 在軟體開發過程中,併發控制是確保及時糾正由併發操作導致的錯誤的一種機制。從 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NET Entity Framework,.NET 都為併發控制提供好良好的支持方案。併發處理方式一般分 ...


原網站:C# Entity Framework併發處理

在軟體開發過程中,併發控制是確保及時糾正由併發操作導致的錯誤的一種機制。從 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NET Entity Framework,.NET 都為併發控制提供好良好的支持方案。
併發處理方式一般分為樂觀必併發與悲觀必併發兩種,本文將為大家介紹 Entity Framework 、 LINQ to SQL 中的併發處理方式。在本文最後,將提供一個了可參考的方案,結合事務與併發控制確保全全的數據交換機制。

目錄

一、併發處理的定義

二、模型屬性的併發處理選項

三、Entity Framewrok 悲觀併發

四、Entity Framework 樂觀併發

五、回顧 LINQ to SQL 併發處理的方式

六、結合事務處理併發衝突

 

 

一、併發處理的定義

在軟體開發過程中,當多個用戶同時修改一條數據記錄時,系統需要預先制定對併發的處理模式。併發處理模式主要分為兩種:
第一種模式稱為悲觀式併發,即當一個用戶已經在修改某條記錄時,系統將拒絕其他用戶同時修改此記錄。
第二種模式稱為樂觀式併發,即系統允許多個用戶同時修改同一條記錄,系統會預先定義由數據併發所引起的併發異常處理模式,去處理修改後可能發生的衝突。常用的樂觀性併發處理方法有以下幾種:

  1. 保留最後修改的值。
  2. 保留最初修改的值。
  3. 合併多次修改的值。

相對於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          public int Update(Person person)
 2          {
 3              int n = -1;
 4              try
 5              {
 6                  using (BusinessEntities context = new BusinessEntities())
 7                  {
 8                      lock (this) 
 9                      {
10                         var obj = context.Person.Where(x => x.Id == person.Id).First();
11                          if (obj != null)
12                              context.ApplyCurrentValues("Person", person);
13                          n = context.SaveChanges();
14                      }
15                  }
16              }
17              catch (Exception ex)
18              { ...... }
19              return n;
20          }
 

使用悲觀併發雖然能有效避免數據發生邏輯性的錯誤,但使用 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 同時更新數據

在系統輸入下麵代碼,先獲取資料庫中的 Id 為24的對象 Person,使用Display方法在數據修改前顯示對象的最初值。然後使用非同步方法分兩次調用Update方法,同時更新Person對象的相關屬性,第一次更新對象的 FirstName 屬性,第二次更新對象的 SecondName、Age 兩個屬性。最後,在使用SaveChanges保存更新後,顯示數據更新後的信息。

 
 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 異常。
遇到此異常時,可以用 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 = "[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,這樣系統就會實時檢測對象屬性的輸入值 。
當該屬性被同時更新,系統便會激發 OptimisticConcurrencyException 異常。捕獲該異常後,可以使用 ObjectContext.Refresh (RefreshMode,object) 刷新上下文中該對象的狀態,當 RefreshMode 為 ClientWins 時,系統將會保持上下文中的現在有數據,即保留最新輸入的對象值。此時再使用ObjectContext.SaveChanges, 系統就會把最新輸入的對象值加入資料庫當中。

在下麵的例子當,系統啟動前先把 Person 的 FirstName、SecondName 兩個屬性的 ConcurencyMode 屬性設置為Fixed,使系統能監視這兩個屬性的更改。所輸入的數據只在FirstName、SecondName 兩個值中作出修改。在數據提交前先以 DisplayProperty 方法顯示資料庫最初的數據屬性,在數據初次更新後再次調用 DisplayProperty 顯示更新後的數據屬性。在第二次更新數據時,將引發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 時,系統就會把數據源中的數據代替上下文中的數據。
因為初次調用 SaveChanges,數據可以成功保存到資料庫。但是在 ObjectContext 並未釋放時,再次使用 SaveChanges 非同步更新數據,就會引發OptimisticConcurrencyException 併發異常。當 RefreshMode 為 StoreWins 時,系統就會保留初次輸入的數據屬性。
此例子與上面的例子十分相似,只是把 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 當中併發處理的方式。
與 Entity Framework 相似,LINQ to SQL 中表格的每個列都為可以設定不同處理方式,屬性 UpdateCheck (更新檢查) 的預設值為 Always,即系統會在預設情況下檢查屬性的併發狀態。若把屬性改為 WhenChanged,即當該屬性發生改變時,系統才會對其進行檢測。若把屬性改為 Nerver , 這時系統將不會對此屬性進行檢查,總是接受最新一次的輸入值。


處理 LINQ to SQL 併發,最為重要的是以下兩個方法:

DataContext.SubmitChanges(ConflictMode)
DataContext.ChangeConflicts.ResolveAll(RefreshMode);

SubmitChanges 將對檢索到的對象所做的更改發送到基礎資料庫,並通過 ConflictMode 指定併發衝突時要採取的操作 。當選擇 ConflictMode.FailOnFirstConflict 時,若檢測到第一個併發衝突錯誤時,系統會立即停止對更新資料庫的嘗試。當選擇 Conflict.ContinueOnConflict 時,系統會嘗試運行對資料庫的所有更新。

 

成員名稱 說明
 FailOnFirstConflict  指定當檢測到第一個併發衝突錯誤時,應立即停止對更新資料庫的嘗試。
 ContinueOnConflict  指定應嘗試對資料庫的所有更新,並且應在該過程結束時累積和返回併發衝突。

ConfilctMode成員圖

 

ResolveAll 能使用指定策略解決集合中的所有衝突,當選擇 RefreshMode.KeepChanges 時,系統會強制 Refresh 方法保持當前上下文的數據值。當選擇RefreshMode.OverwriteCurrentValues, 系統會用資料庫的值覆蓋當前上下文中的數據值。當選擇 RefreshMode.KeepCurrentValues, 系統會把當前上下文的更新值與資料庫中的值進行合併處理

 

成員名稱     說明
OverwriteCurrentValues 強制 Refresh 方法使用資料庫中的值重寫所有當前值。
KeepChanges 強制 Refresh 方法保留已更改的當前值,但將其他值更新為資料庫值。
KeepCurrentValues 強制 Refresh 方法使用從資料庫檢索的值替換原始值。 不會修改當前值。

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#綜合揭秘——細說事務”。
使用樂觀性併發,在發生併發異常時保留最初輸入值,並利用事務,對引起 OptimisticConcurrencyException 異常的修改進行回滾,最後把引起異常的數據與資料庫中的已有數據以頁面或者視窗的形式顯示給客戶進行對比,這是最為常用的數據處理方式之一。這樣既可以保持系統高效運行,也可以避免系統數據出現邏輯性的錯誤。
下麵舉個例子,對此方法進行說明。首先把 Person 對象的 FirstName、SecondName 屬性的 ConcurencyMode 設置為 Fixed 後,系統會同時更新 Id 等於 52 的 Person 對象這兩個屬性,更新的操作會以 TransactionScope 進行監控。在同時更新屬性時,系統會釋放出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\n  Person", 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\n  Person", afterObj);
41  
42              Console.ReadKey();
43          }
44  
45          public Person GetPerson(int id)
46          {
47              using (BusinessEntities context = new BusinessEntities())
48              {
49                  IQueryable<Person> list=context.Person.Where(x => x.Id == id);
50                  return list.First();
51              }
52          }
53  
54          //更新對象
55          public void Update(Person person)
56          {
57              using (BusinessEntities context = new BusinessEntities())
58              {
59                  var obj = context.Person.Where(x => x.Id == person.Id).First();
60                  if (obj != null)
61                  {
62                      try
63                      {
64                          using (TransactionScope scope = new TransactionScope())
65                          {
66                              context.ApplyCurrentValues("Person", person);
67                              //虛擬操作,保證數據被同步載入
68                              Thread.Sleep(100);
69                              int n = context.SaveChanges();
70                              scope.Complete();
71                          }
72                      }
73                      catch (System.Data.OptimisticConcurrencyException ex)
74                      {
75                          //可以根據需要,以錯誤頁面或者提示視窗等方式處理
76                          //顯示發生OptimisticConcurrencyException異常所輸入的數據屬性
77                          DisplayProperty("OptimisticConcurrencyException\n  Client Value:", person);
78                          DisplayProperty("  Database Value:", GetPerson(person.Id));
79                      }
80                      catch(Exception ex)
81                      {......}
82                  }
83              }
84          }
85  
86          //顯示對象相關屬性
87          public void DisplayProperty(string message, Person person)
88          {
89              String data = string.Format("{0}\n    Id:{1}  FirstName:{2}  " +
90                  "SecondName:{3}  Age:{4}\n    Address:{5}  Telephone:{6}  EMail:{7}\n",
91                  message, person.Id, person.FirstName, person.SecondName, person.Age,
92                  person.Address, person.Telephone, person.EMail);
93              Console.WriteLine(data);
94          }
95      }
複製代碼

觀察測試結果,當系統發生併發異常時,資料庫只會保留首次輸入的更新值。值得註意的是:使用此數據處理方式與悲觀併發方式最大區別在於,傳統的悲觀併發處理方式不允許同一時刻有多個客戶端處理同一個數據表中的相同對象,但因為客觀因數的影響,系統難以仔細辨認客戶同時進行修改的是否同一個數據項,所以基本的做法是使用鎖把更新對象進行鎖定。但如此一來,無論客戶同時更新的是否同一個數據項,操作都將會被延時或禁止。換句話說,無論需要更新的是相同對象還是不同對象,客戶端都不能同時更新同一個數據表。若使用此樂觀併發方式,系統允許多個客戶端同時處理同一個數據表。如果所處理的是數據表中的不同對象,操作可以順利地進行而不會相互影響。如果所處理的是數據表中的相同對象,操作將會保存第一次輸入的對象值。

 

 回到目錄

總結

併發話題與線程、進程等其他話題有所不同,它並沒有複雜的類和方法。處理併發來來去去都是簡單的幾行代碼,它所重視的是併發異常發生後所帶來的後果與處理方式。與中國傳統的太極相似,併發只重其意,不重其招,只要深入地瞭解其過程,考慮其可能帶來的問題後,你便可以對其收發自如。 
對併發的處理應該針對特定的問題,分別對待。到最後你可能發現,原來微軟早已為你定製好處理的方式,可能 “回到原點” 什麼都不做就是最好的處理方式。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [1]文件類型 [2]文件屬性 [3]目錄路徑 [4]目錄遍歷 [5]目錄統計 [6]目錄增刪 [7]目錄複製 [8]文件操作 [9]文件內容 ...
  • 一.OGNL的概念 OGNL是Object-Graph Navigation Language的縮寫,全稱為對象圖導航語言,是一種功能強大的表達式語言,它通過簡單一致的語法,可以任意存取對象的屬性或者調用對象的方法,能夠遍歷整個對象的結構圖,實現對象屬性類型的轉換等功能。 Struts 2支持以下幾 ...
  • HTTP Status 500 - Wrapper cannot find servlet class com.servlet.servlet.RegServlet or a class it depends on type Exception report message Wrapper cann ...
  • C語言是我們大多數人的編程入門語言,對其也再熟悉不過了,不過很多初學者在學習的過程中難免會出現迷茫,比如:不知道C語言可以開發哪些項目,可以應用在哪些實際的開發中……,這些迷茫也導致了我們在學習的過程中不知道如何學、學什麼,所以,總結這個列表,希望對C語言初學者可以有所幫助~ C語言可以做什麼? 從 ...
  • 什麼是Hibernate? Hibernate是基於ORM(O:對象,R:關係,M:映射)映射的持久層框架,是一個封裝JDBC的輕量級框架,主要實現了對資料庫的CUPD操作。 註:CRUD是指在做計算處理時的增加(Create)、查詢(Retrieve)(重新得到數據)、更新(Update)和刪除( ...
  • 學習這個東西挺奇怪的,時間一長就容易忘記,或者記不清楚。今天看到一些UML圖的關係,發現有些出入了,索性就寫下來,以後再忘記的時候過來看看。 在UML的類圖中,常見的有以下幾種關係: 繼承(Generalization), 實現(Realization), 關聯(Association), 依賴(D ...
  • 今天講單例設計模式,這種設計模式和工廠模式一樣,用的非常非常多,同時單例模式比較容易的一種設計模式。 一、什麼是單例設計模式 單例模式,也叫單子模式,是一種常用的軟體設計模式。在應用這個模式時,單例對象的類必須保證只有一個實例存在。 二、單例模式的技巧 三、單例模式的應用場景 資料庫設計,我們發送一 ...
  • 設計模式;一個程式員對設計模式的理解:“不懂”為什麼要把很簡單的東西搞得那麼複雜。後來隨著軟體開發經驗的增加才開始明白我所看到的“複雜”恰恰就是設計模式的精髓所在,我所理解的“簡單”就是一把鑰匙開一把鎖的模式,目的僅僅是著眼於解決現在的問題,而設計模式的“複雜”就在於它是要構造一個“萬能鑰匙”,目的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...