如何在項目中應用LinqToSql數據庫事務

作者: ryanding  來源: 博客園  發布時間: 2010-12-26 21:38  閱讀: 939 次  推薦: 1   原文鏈接   [收藏]  

  本文主要涉及LinqToSql數據庫事務相關,文章不足之處,歡迎您指出。

  一、回顧T-SQL中的事務機制

  代碼如下:

 
1 /*加入事務機制后的存儲過程*/
2  create procedure sp_example
3 @param1 int = null,
4 @param2 nvarchar(20) = null
5  as
6 begin tran tranName /*sql 事務的加入*/
7 insert into table0 (col1,col2,col3) values ('value1','value2','value3')
8 update table1 set column1 = @param1 where 1=1
9 --刪除table2中一條已經被其他外鍵表引用的記錄,此時會報sql引用錯誤
10   delete from table2 where column3 = @param1
11 insert into table3 (col1,col2) values ('value1','value2')
12 if(@@error =0)
13 commit tran tranName
14 else
15 rollback tran tranName
16  go

  以上代碼是一個具備事務機制的簡單存儲過程,需要指出的是當上述代碼執行到第十行時,此時如果該存儲過程未加入事務機制那么勢必會導致第10行之前已經被影響的數據庫記錄也不會被還原(rollback)。這樣的代碼是我們不想見到的,所以事務在復雜的商業邏輯中保持數據的完整性還是尤為重要的。

  二、LinqToSql 中的SubmitChanges內置事務機制

  眾所周知LinqToSql 中我們的事務機制代碼變的相對簡單了,如以下代碼:

 
1 public bool DeleteDepartment(int departmentId)
2 {
3 try
4 {
5 DataContext.SystemUser.DeleteOnSubmit(
6 DataContext.SystemUser.FirstOrDefault(u => u.DepartmentID == departmentId));
7
8 DataContext.Department.DeleteOnSubmit(
9 DataContext.Department.FirstOrDefault(f => f.DepartmentID == departmentId));
10
11 //事務機制被封裝到SubmitChanges方法內
12   DataContext.SubmitChanges();
13
14 return true;
15 }
16 catch
17 {
18 return false;
19 }
20 }

  上述代碼很容易理解,在LinqToSql 為了刪除一條部門記錄。我們首選要刪除該部門被引用的外鍵表記錄這里是員工表,(以上代碼只是為舉例用,實際開發中是不會有此種業務的)當外鍵記錄都刪除成功后代碼執行到第8行,這時才能能刪除部門對象。否則報SqlException外鍵引用無法刪除部門記錄。我們唯一需要做的只是將 DataContext.SubmitChanges();這句放在所有Linq操作數據庫語句之后這樣就可以調用數據庫事務機制了。比如當第5行代碼執行時SystemUser還被Order表引用。當SubmitChanges執行時會自動調用transaction.Rollback()方法回滾SubmitChanges()之前的所有被影響的數據庫記錄,詳情請閱Reflector。

  三、在LinqToSql中SubmitChange內置事務機制無法滿足的業務場景

  當程序需要處理更多更復雜的商業邏輯時,我發現光憑SubmitChange方法自帶的事務機制是遠遠不能滿足的。

  該場景描述如下:

  如果為完成某一個特定的業務,需要在程序中使用多次的SubmitChanges方法。比如我們要做一個庫存相關業務,該業務是由兩張表組成:主表+從表。分別為主表:Depot和從表:DepotDetail 兩張表。兩張表關系如下:

  當我們通過LinqToSql生成一個庫存對象時其實應先生成Depot對象后再將生成Depot對象的DepotID(主鍵)傳遞到DepotDetail對象中用于生成庫存明細表記錄。也就說為了生成庫存明細表記錄我們必須先生成Depot主表,那樣就不得不先調用SubmitChanges方法,當保存DepotDetail對象時還需要再一次調用SubmitChanges()方法。因為調用了多次SubmitChanges方法所以SubmitChanges內置的回滾機制已經不能滿足需要了。

  四、TransactionScope的應用

   我們需要引用.net 的System.Transactions 類庫使用TransactionScope類,幫我們更有效的處理數據庫事務機制。對TransactionScope進行封裝,代碼如下:

  本段代碼原作者,被我稍稍改造如下: 

 
1 public static class DBTransactionExtension
2 {
3 public static bool Excute(out string errorMsg, params Action[] actions)
4 {
5 //使用ReadCommitted隔離級別,保持與Sql Server的默認隔離級別一致
6 return Excute(out errorMsg, IsolationLevel.ReadCommitted, null, actions);
7
8 }
9
10 public static void Excute(out string errorMsg, IsolationLevel level, params Action[] actions)
11 {
12 Excute(out errorMsg, level, null, actions);
13 }
14
15 public static void Excute(out string errorMsg, int timeOut, params Action[] actions)
16 {
17 Excute(out errorMsg, IsolationLevel.ReadCommitted, timeOut, actions);
18 }
19
20 public static bool Excute(out string errorMsg, IsolationLevel level, int? timeOut, params Action[] actions)
21 {
22 errorMsg = "";
23 if (actions == null || actions.Length == 0)
24 return false;
25 TransactionOptions options = new TransactionOptions();
26
27 options.IsolationLevel = level; //默認為Serializable,這里根據參數來進行調整
28
29 if (timeOut.HasValue)
30
31 options.Timeout = new TimeSpan(0, 0, timeOut.Value); //默認60秒
32
33 using (TransactionScope tran = new TransactionScope(TransactionScopeOption.Required, options))
34 {
35 try
36 {
37 Array.ForEach<Action>(actions, action => action());
38 tran.Complete(); //通知事務管理器它可以提交事務
39 return true;
40 }
41 catch (Exception ex)//回滾事務
42 {
43 errorMsg = ex.Message;
44 return false;
45 }
46 }
47 }
48
49 }

  調用DBTransactionExtension代碼如下:

 
1 private void SaveDepot(Depot depot)
2 {
3 DataContext.Depots.InsertOnSubmit(depot);
4
5 if (false)//TODO:保存庫存主表前的邏輯判斷,條件不滿足時候調用 throw new exception執行TransactionScope回滾。
6   throw new Exception("自定義錯誤提示內容,最終由事務獲取錯誤信息后拋給UI");
7
8 //條件滿足則調用SubmitChanges
9   DataContext.SubmitChanges();
10 DepotDetail depotDetail = new DepotDetail();
11 depotDetail.DepotID = depot.DepotID;
12 depotDetail.Count = 100;
13
14 DataContext.DepotDetails.InsertOnSubmit(depotDetail);
15 //又調用了一次SubmitChanges
16   DataContext.SubmitChanges();
17 }
18 public Depot InvokeTransaction(Depot depot, out string errorMsg)
19 {
20 try
21 {
22 DBTransactionExtension.Excute(out errorMsg, () => SaveDepot(depot));
23 return depot;
24 }
25 catch (Exception ex)
26 {
27 errorMsg = ex.Message;
28 return null;
29 }
30 }

  根據上述調用方法,我們已經可以在LinqToSql中靈活的使用數據庫事務了。

  五、TransactionScope類使用的注意事項

  使用TransactionScope時如果調用多次LinqToSql的DataContext對象實例(等同調用多個數據庫連接),那么我們必須開啟MSDTC否則事務不能正常工作,具體請閱MSDTC開啟。注:TransactionScope 適用于多種 Data Provider 比如 oracle 、OleDB、ODBC等。

  最后希望本篇文章能給您帶來幫助。

1
0
 
標簽:LinqToSql
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()