本文主要涉及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 兩張表。兩張表關系如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiInBnauYzYkNTO4YzYhVWM3cDN2EjZ4czNzETZ0kzYmdjMmFjYfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.jpg)
當我們通過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類使用的注意事項
最後希望本篇文章能給您帶來幫助。