天天看點

一起談.NET技術,如何在項目中應用LinqToSql資料庫事務

  本文主要涉及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 兩張表。兩張表關系如下:

一起談.NET技術,如何在項目中應用LinqToSql資料庫事務

  當我們通過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);

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)

3 DataContext.Depots.InsertOnSubmit(depot);

4

5 if (false)//TODO:儲存庫存主表前的邏輯判斷,條件不滿足時候調用 throw new exception執行TransactionScope復原。

6   throw new Exception("自定義錯誤提示内容,最終由事務擷取錯誤資訊後抛給UI");

8 //條件滿足則調用SubmitChanges

9   DataContext.SubmitChanges();

10 DepotDetail depotDetail = new DepotDetail();

11 depotDetail.DepotID = depot.DepotID;

12 depotDetail.Count = 100;

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類使用的注意事項

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