處理 EF 併發其實就這麼簡單

Zery發表於2013-08-19

    最近專案有點閒,終於可以瞭解點自己想了解的了,以前聽同事講面試的經歷總會被問到“如何處理高併發大資料” 乍一聽感覺這東西好像很有學問的樣子,於是併發這個詞在腦海裡留深刻印像,而且在自己心中的技術地位也提高很多,也導致瞭解併發相關的知識時,也帶著思想負擔,總以為很難懂,程式設計師或許都是這樣,在自己不懂的技術領域,別人說一個很簡單的技術,給他的感覺都是很高深的樣子,其實自己一瞭解就會發現,“哎喲 我 C 原來就這樣兒啊!”總之只要你想了解,花點時間就沒有難的事! 好了 正式進入正題

EF併發處理 在此之前先把與併發相關的一此知識點給大家介紹一下,有些雖然不是瞭解併發的必要條件,但我認為多瞭解一點又有什麼不好呢?

一 EF併發介紹

 

    什麼叫併發:當多個使用者同時更新同一資料的時候,由於更新可能導致資料的不一致性,使得程式的業務資料發生錯誤,這種情況可以稱之為併發。

併發又分為兩種:樂觀併發 與 悲觀併發 

  樂觀併發即系統允許多個使用者同時修改同一條記錄,系統會預先定義由資料併發所引起的併發異常處理模式,去處理修改後可能發生的衝突

當出現樂觀併發時應該怎麼處理呢,通常有如下三種處理方法 

     a 保留最後一次物件修改的值

   b 保留最初的修改值

   c  合併修改值

這三種處理方法下文會一一介紹

     悲觀併發:在同一時刻只允許一個使用者修改相同資料,直接用Lock 與 unLock就可以處理,後文就不再解釋了 

 

二  EF物件狀態

 

在EF中所有的物件狀態只有被新增到ObjectContext 上下文中才能被跟蹤,才能進行持久化操作,那麼在ObjectContext中對於物件狀態分幾種呢?有如下五種

Detached:物件存在,但未由物件服務跟蹤。在建立實體之後、但將其新增到物件上下文之前,該實體處於此狀態
Unchanged: 自物件載入到上下文中後,或自上次呼叫 System.Data.Objects.ObjectContext.SaveChanges() 方法後,此物件尚未經過修改
Added: 物件已新增到物件上下文,但尚未呼叫 System.Data.Objects.ObjectContext.SaveChanges() 方法
Deleted:使用 System.Data.Objects.ObjectContext.DeleteObject(System.Object) 方法從物件上下文中刪除了物件
Modified:物件已更改,但尚未呼叫 System.Data.Objects.ObjectContext.SaveChanges() 方法
這些狀態 在瞭解併發的原理時會用到
 

三  EF事務隔離級別

在這裡只例舉三個最常用到的隔離級別,其它的有待朋友們自己行研究了

ReadCommitted:不可以在事務期間讀取可變資料,但是可以修改它(EF 預設的隔級別)

ReadUncommitted:可以在事務期間讀取和修改可變資料。

Serializable:可以在事務期間讀取可變資料,但是不可以修改,也不可以新增任何新資料。

隨著隔離級別的提高,可以更有效地防止資料的不一致性。但是,這將降低事務的併發處理能力,會影響多使用者訪問

 

四 事務不同隔離級別帶來資料讀取的不同結果

髒讀:當一個事務讀取資料修改後以經SaveChange但事務還沒有提交,此時另外一個事務就讀取了該資料,此時的資料就是髒資料,這一過程就是髒讀

不可重複讀是指在一個事務內,多次讀同一資料。在這個事務還沒有結束時,另外一個事務也訪問該同一資料。那麼,在第一個事務中的兩次讀資料之間,由於第二個事務的修改,那麼第一個事務兩次讀到的的資料可能是不一樣的。這樣就發生了在一個事務內兩次讀到的資料是不一樣的這一過程就是不可重複讀

幻讀:一個事務針對一張表的所有資料進行讀取修改,而此時另一個事務向表中插入了一條資料,則第一個事務資料不包含新資料,像出現幻覺一樣,這一過程就是幻讀

ReadCommitted 會引發 不可重複讀 和幻讀

ReadUncommitted 會引發 髒讀 ,不可重複讀 和幻讀

Serializable 以上三種都不會發生 

可見 Serializable的隔離級別是最高的,資料也是最準確的,但是高正確率也是要付出代價的,在此種隔離級別下,讀取資料,與更新資料的效率也是最低的

 

五 EF併發的處理流程

    對於併發原理是這樣的,每一次操作表 欄位TimeStamp的值都會改變,而每次對錶做操作時都會比對物件的TimeStamp值與資料庫中表的值是否相同,是則操作表,否 ,則丟擲異常給客戶端或是重新整理物件狀態後重新儲存,詳細步驟如下

  1 在表中新建一個欄位型別為TimeStamp , TimeStamp型別在.netFrameWork中是一個8位的陣列,在SQL中則是一串二進位制字元

  2 在.edmx 物件實體模型圖中,右鍵TimeStamp欄位 屬性-->併發模式-->選擇Fixed

      

     注意:把併發模式 設為Fixed後每一次操作表都會把TimeStamp欄位當做條件查詢,只有相等才能成功,以下是用SQL Profile跟蹤到的結果 在 where 處可以看到效果

     

     3 模擬併發,通過捕獲異常在異常處返回訊息給客戶端 異常型別有如下兩種 

       3.1 EF 自定義異常:System.Data.Entity.Infrastructure.DbUpdateConcurrencyException 
       3.2 .net FrameWorkn異常:System.Data.OptimisticConcurrencyException
 
     4 併發處理完畢
 
具體程式碼如下
 1             //第一次載入物件更新後暫不儲存到資料庫
 2             var fContext = new BolgModelEntities();
 3             var menuObj = fContext.Menu.FirstOrDefault();
 4             menuObj.MenuName = "C#";
 5 
 6             using (var sContext = new BolgModelEntities())
 7             {
 8                 //第二次載入物件更新後,儲存到資料庫 此時TimeStamp的值已改變,與menuObj物件的TimeStamp值已不同,所以在menuObj儲存時會拋異常出來
 9                 var obj = sContext.Menu.FirstOrDefault();
10                 obj.MenuName = "WPF";
11                 sContext.SaveChanges();//可以順利儲存
12             }
13 
14             try
15             {
16                 //儲存會拋異常,因為TimeStamp 值不匹配
17                 fContext.SaveChanges();
18             }
19             //catch (DbUpdateConcurrencyException ex) //EF 自定義異常
20             //{
21                 
22             //    fContext.Refresh(RefreshMode.ClientWins, menuObj);
23             //    fContext.SaveChanges();
24             //}
25             catch (System.Data.OptimisticConcurrencyException ex) //.net FreamWork 定義異常
26             {
27                 //捕獲異常後依然對資料進行儲存
28                 fContext.Refresh(RefreshMode.ClientWins, menuObj); // RefreshMode.ClientWins 儲存物件更新後的值 且物件的狀態為Modified。
29 
30                 fContext.Refresh(RefreshMode.StoreWins, menuObj);//RefreshMode.StoreWins 儲存資料庫中原有值, 且物件的狀態為Modified
31 
32                 fContext.SaveChanges();//SaveChanges完成後物件狀態變為Unchanged
33                 
34             }
35             catch (System.Data.OptimisticConcurrencyException ex)
36             {
37                 //捕獲異常後不再處理,將訊息返回給客戶端
38                 string message = string.Empty;
39                 message = "出現資料衝突請重新提交";
40                 return message;
41             }

 

在併發丟擲異常後可以根據業務需,向客戶端返回訊息,

也可以直接處理衝突後的資料

     a 保留最後一次物件修改的值 用 RefreshMode.ClientWins

   b 保留最初的修改值   用 RefreshMode.StoreWins

   c  合併修改值  針對同物件不同屬性一樣可以 用 RefreshMode.StoreWins

好了到此 關於EF的併發就寫完了,當然我只是把併發的基礎說了一下,對於更高效,科學的併發,還需要朋友根據自己專案 的情況來做相應的處理

其實 寫完了才發現關於EF的併發 其實並沒有想象中的那麼麻煩,只要多花點時間,多看看資料,問題就不大了

對於以上內容,如有不對之處,還望各位能指出,不要讓我誤導了他人,

另外如果覺得,本文對你有那麼一點幫助,還望不吝嗇的點一點 推薦您的推薦將是我源源不斷的寫作力

 

相關文章