Linq to Sql : 三种事务处理方式
??? Linq to SQL支持三種事務處理模型:顯式本地事務、顯式可分發事務、隱式事務。(from? MSDN:?事務 (LINQ to SQL))。MSDN中描述得相對比較粗狂,下面就結合實例來對此進行闡述。
0. 測試環境
| OS | Windows Server 2008 Enterprise + sp1 |
| IDE | Visual Studio 2008, .net framework 3.5 + SP1 |
| DB | SQL Server 2000 + sp4 SQL Server 2008 Enterprise Edition |
?
1. 隱式事務
??? 當調用SubmitChanges 時,L2S會檢查當前環境是否開啟了事務(下面兩節中創建的事務),如果沒有開始事務,則 L2S啟動本地事務(IDbTransaction),并使用此事務執行所生成的 SQL 命令。
??? 使用Reflector查看DataContext.SubmitChanges的源代碼(見本文最后的附錄),可以看到,如果開啟了一個事務,并將其傳給DataContext.Transaction,則在執行SubmitChanges時就不會再重新開啟一個新的事務了。
??? 例如,下面的代碼會創建應隱式事務:
1: public static void TestTranIn2000() 2: { 3: using (SQL2000.Sql2000DataContext context1 = new SQL2000.Sql2000DataContext()) 4: { 5: List<SQL2000.TLINQ> linq = context1.TLINQ.ToList(); 6: linq.ForEach(e => e.Value += e.Value); 7: context1.SubmitChanges(); 8: } 9: }??? 可以打開SQL Server Profile來查看生成的T-SQL,生成的Update語句被包含在Begin Transaction和Commit Transaction之間,如下圖所示:
??? 上圖是我使用Linq to SQL + SQL Server 2000進行測試的結果。下圖是我用Linq to SQL + SQL Server 2008測試的結果:
??? 很奇怪的是,居然沒有Begin Transaction和Commit Transaction了。Begin/commit trand的事件類型是“SQL:BatchStarting”/“SQL:BatchCompleted”,從這個圖中可以看到,我有跟蹤這個事件(譬如第一個框中的Select命令),汗……是MSDN上說錯了?
??? 抱著這個疑問,我又針對Linq to Sql + SQL Server 2008做了進一步測試(這個例子也可以用來測試后面兩節中的事務處理,確保所有操作被分封裝在同一個事務中。):
??? 這里里面做了兩件事:創建一個新對象,同時還修改原有記錄中的值。我故意Insert可以執行成功,而讓Update語句執行出錯;如果有啟用事務,則出錯后事務會回滾,最終不會創建新記錄;如果沒有啟用事務,則可以正常插入,但不會執行第二步中的更新。
??? 注意:從理論上講,執行SubmitChanges時,并不一定是按照上面代碼的順序,先執行插入再執行更新;下面是MSDN上的說法:
| ??? 當您進行此調用時,DataContext 會設法將您所做的更改轉換為等效的 SQL 命令。您可以使用自己的自定義邏輯來重寫這些操作,但提交順序是由 DataContext 的一項稱作“更改處理器”的服務來協調的。事件的順序如下: 當您調用 SubmitChanges 時,LINQ to SQL 會檢查已知對象的集合以確定新實例是否已附加到它們。如果已附加,這些新實例將添加到被跟蹤對象的集合。 所有具有掛起更改的對象將按照它們之間的依賴關系排序成一個對象序列。如果一個對象的更改依賴于其他對象,則這個對象將排在其依賴項之后。 在即將傳輸任何實際更改時,LINQ to SQL 會啟動一個事務來封裝由各條命令組成的系列。 對對象的更改會逐個轉換為 SQL 命令,然后發送到服務器。 ??? 此時,如果數據庫檢測到任何錯誤,都會造成提交進程停止并引發異常。將回滾對數據庫的所有更改,就像未進行過提交一樣。DataContext 仍具有所有更改的完整記錄。 |
??? 因此,這里還是打開SQL Server Profile來確認:
??? OK,現在可以放心了,這里的確是先執行Insert,再執行Update;執行Update時出現了SqlException異常。這時查看測試表TLINQ中的數據,發現沒有插入新的記錄進去。也就是說,這里使用了事務,但是SQL Server Profile沒有跟蹤到。
??? 至于為啥會這樣,我暫時也沒有搞清楚;還有就是下面一節中即使執行DbConnection.BeginTransaction(),也不會跟蹤到begin tran和commit tran。不知道是不是SQL Server 2008里面升級了啥。哪位如果知道原因,麻煩告知我一聲,謝謝。
??? 最后總結一下隱式事務的優缺點:
??? 優點:使用簡單,L2S都幫我們搞定了,我們不需要寫任何代碼。
??? 缺點:只能處理單個DataContext中的單個SubmitChanges()函數的調用。如果需要將SubmitChanges()與其他自定義更新操作(譬如ExcuteCommand)共用一個Transaction、或者與其他DataContext共用一個DBTransation,就沒轍了.......
?
2. 顯式本地事務
??? SubmitChanges只能處理最基本的CUD(Create/Update/Delete)操作;而在日常的應用中,邏輯往往沒有這么簡單,或者考慮性能等因素,我們需要配合ADO.Net執行原生的TSQL;這時我們可能要讓ADO.Net + Linq to SQL來進行配合處理。
??? 也就是說,我們可以手工創建一個DbConnection和DbTransaction,然后傳給DataContext,代碼示例如下:
1: public static void TestTranInSQL2008() 2: { 3: using(SqlConnection conn = new SqlConnection(Settings.Default.AdventureWorksConnectionString)) 4: { 5: conn.Open(); //1. 創建并打開DbConnection連接 6: using (SqlTransaction tran = conn.BeginTransaction()) //2. 開啟DbTransaction 7: { 8: //3. 使用ADO.Net執行TSQL 9: SqlCommand cmd = new SqlCommand("Update TLinq SET Value=10", conn, tran); 10: cmd.ExecuteNonQuery(); 11:? 12: //4. 配合Ado.Net和Linq to Sql: 將ADO.Net的DbConnection和DbTransaction傳給Linq to Sql 13: using (AdventureWorksDataContext context1 = new AdventureWorksDataContext(conn)) 14: { 15: context1.Transaction = tran; 16: List<TLINQ> linq = context1.TLINQ.ToList(); 17: context1.TLINQ.InsertOnSubmit(new TLINQ() { Value = "1" }); 18: linq.ForEach(e => e.Value = (Convert.ToInt32(e.Value) + 1).ToString()); 19: context1.SubmitChanges(); 20: } 21:? 22: tran.Commit(); //5. 需要提交手工創建的事務 23: } 24: } 25: }??? 最后總結一下使用顯式本地事務的優缺點:
??? 優點:可以配合Ado.Net一起使用,或者跨DataContext使用,實現負責的業務邏輯。
??? 缺點:處理步驟比較繁瑣。L2S中的DataContext已經提供了ExcuteCommand方法來執行原生的TSQL,這里還這樣使用Ado.net就太折騰自己了.......
?
3. 顯式可分發事務
??? 使用TransactionScope來進行顯示可分發事務控制。TransactionScope就像事務處理里面的一面魔鏡,如果需要事務,就對著它喊:“主啊,請賜我事務!”,于是這個操作就有了事務功能。關于TransactionScope的詳細介紹,可以參考MSDN:使用事務范圍實現隱式事務,及SQL Server的聯機叢書:CLR 集成和事務
??? 使用顯式可分發事務進行處理的示例代碼如下:
1: public static void TestTransactionScopeInSQL2008() 2: { 3: Action action = () => //1.把要執行的操作封裝在一個或多個Action中 4: { 5: using (AdventureWorksDataContext context1 = new AdventureWorksDataContext()) 6: { 7: context1.ExecuteCommand("Update TLinq SET Value={0}", 10); 8: List<TLINQ> linq = context1.TLINQ.ToList(); 9: context1.TLINQ.InsertOnSubmit(new TLINQ() { Value = "1" }); 10: linq.ForEach(e => e.Value = (Convert.ToInt32(e.Value) + 1).ToString()); 11: context1.SubmitChanges(); 12: } 13: }; 14: TransactioExtension.Excute(action); 15: }??? 或者這樣:
1: /// <summary> 2: /// 此方法里面完全不必考慮事務 3: /// </summary> 4: public static void TestTransactionScopeInSQL2008() 5: { 6: using (AdventureWorksDataContext context1 = new AdventureWorksDataContext()) 7: { 8: context1.ExecuteCommand("Update TLinq SET Value={0}", 10); 9: List<TLINQ> linq = context1.TLINQ.ToList(); 10: context1.TLINQ.InsertOnSubmit(new TLINQ() { Value = "1" }); 11: linq.ForEach(e => e.Value = (Convert.ToInt32(e.Value) + 1).ToString()); 12: context1.SubmitChanges(); 13: } 14: } 15:? 16: //而在外面直接這樣使用 17: TransactioExtension.Excute(() => TestTransactionScopeInSQL2008());??? 灰常灰常地簡潔,把要執行的操作封裝在一個或多個Action中,然后傳遞給TransactioExtension.Excute即可。對于封裝在TransactionScope里面執行的所有操作(譬如SubmitChanges,ExcuteCommand、ExecuteQuery),最終都會解析為對ADO.NET的調用;而ADO.Net會在調用 Connection.Open 方法時自動檢查Transaction.Current,并在該事務中以透明方式登記連接(除非在連接字符串中將?Enlist?關鍵字設置為 false)。
??? SqlConnection?對象的?ConnectionString?屬性支持?Enlist?關鍵字,該關鍵字指示?System.Data.SqlClient?是否檢測事務上下文并在分布式事務中自動登記連接。如果此關鍵字設置為 True(默認設置),則會在打開的線程的當前事務上下文中自動登記連接。如果此關鍵字設置為 False,則 SqlClient 連接不會與分布式事務交互。如果未在連接字符串中指定?Enlist,并且如果在打開相應連接時檢測到一個分布式事務,則會在此分布式事務中自動登記此連接。(FROM? Sql Server 2008 聯機叢書)
??? 關于TransactioExtension的封裝,代碼如下所示:(由于TransactionScope默認的事務隔離級別是IsolationLevel.Serializable,這里調整為ReadCommitted隔離級別,以保持與Sql Server的默認隔離級別一致):
1: public static class TransactioExtension 2: { 3: public static void Excute(params Action[] actions) 4: { 5: //使用ReadCommitted隔離級別,保持與Sql Server的默認隔離級別一致 6: Excute(IsolationLevel.ReadCommitted, null, actions); 7: } 8:? 9: public static void Excute(IsolationLevel level, params Action[] actions) 10: { 11: Excute(level, null, actions); 12: } 13:? 14: public static void Excute(int timeOut, params Action[] actions) 15: { 16: Excute(IsolationLevel.ReadCommitted, timeOut, actions); 17: } 18:? 19: public static void Excute(IsolationLevel level, int? timeOut, params Action[] actions) 20: { 21: if (actions == null || actions.Length == 0) 22: return; 23:? 24: TransactionOptions options = new TransactionOptions(); 25: options.IsolationLevel = level; //默認為Serializable,這里根據參數來進行調整 26: if(timeOut.HasValue) 27: options.Timeout = new TimeSpan(0, 0, timeOut.Value); //默認60秒 28: using (TransactionScope tran = new TransactionScope(TransactionScopeOption.Required, options)) 29: { 30: Array.ForEach<Action>(actions, action => action()); 31: tran.Complete(); //通知事務管理器它可以提交事務 32: } 33: } 34: }??? 不過在使用TransactionScope時,需要注意,如果使用的數據庫是SQL Server 2000,或者需要實現跨多個數據庫進行事務控制,則需要開啟DTC服務(位于:開始->管理工具->服務->Distributed Transaction Coordinator),下面是我的測試結果(我沒有裝SQL Server 2005,所以沒測2005的情況):
| 測試環境 | 是否需要開啟DTC | 創建出來的事務類型 |
| Linq to Sql + Sql Server 2000(單一數據庫) | 需要 | 分布式事務 |
| Linq to Sql + Sql Server 2008(單一數據庫) | 不需要 | 輕型本地事務 |
| Linq to Sql + Sql Server 2008(跨多個數據庫) | 需要 | 訪問第一個數據庫時,會創建輕型本地事務;當繼續訪問更多的數據庫時,會將事務升級為完全可分發的分布式事務 |
??? 最后總結一下使用顯式可分發事務的優缺點:
??? 優點:使用簡單,可以配合ADO.Net或者DataContext.ExcuteCommand使用,可以跨DataContext使用,可以跨數據庫使用,可以跨服務器使用。
??? 缺點:分布式事務通常會使用大量的系統資源。Microsoft 分布式事務處理協調器 (MS DTC) 會管理此類事務,并集成在這些事務中訪問的所有資源管理器。慶幸的是:在打開一個具有活動TransactionScope事務的連接而未打開任何其他連接的情況下,該事務會以輕型事務的形式提交,而不是產生完全分布式事務的額外開銷。
?
最后來個匯總:
| 事務類型 | 優點 | 缺點 |
| 隱式事務 | 使用簡單,由L2S自動處理。 | 僅限于單個DataContext中的SubmitChanges方法內使用。 |
| 顯式本地事務 | 可以配合Ado.Net一起使用,可以跨多個DataContext來使用 | 使用相對繁瑣一點兒;且不支持與DataContext.ExecuteCommand配合使用 |
| 顯式可分發事務 | 功能強大(可以配合ADO.Net或者DataContext.ExcuteCommand使用,可以跨DataContext使用,可以跨數據庫使用,可以跨服務器使用),使用簡單 | 可能會對性能有一些影響(我暫時也沒有測試過-,-) |
?
?
?
?
?
?
附:用Reflector查看DataContext.SubmitChanges的源代碼如下:
1: public virtual void SubmitChanges(ConflictMode failureMode) 2: { 3: this.CheckDispose(); 4: this.CheckNotInSubmitChanges(); 5: this.VerifyTrackingEnabled(); 6: this.conflicts.Clear(); 7: try 8: { 9: this.isInSubmitChanges = true; 10: if ((Transaction.Current == null) && (this.provider.Transaction == null)) //如果不在事務環境內 11: { 12: bool flag = false; 13: DbTransaction transaction = null; 14: try 15: { 16: if (this.provider.Connection.State == ConnectionState.Open) 17: { 18: this.provider.ClearConnection(); 19: } 20: if (this.provider.Connection.State == ConnectionState.Closed) 21: { 22: this.provider.Connection.Open(); 23: flag = true; 24: } 25: transaction = this.provider.Connection.BeginTransaction(IsolationLevel.ReadCommitted); //開啟事務 26: this.provider.Transaction = transaction; 27: new ChangeProcessor(this.services, this).SubmitChanges(failureMode); 28: this.AcceptChanges(); 29: this.provider.ClearConnection(); 30: transaction.Commit(); 31: return; 32: } 33: catch 34: { 35: if (transaction != null) 36: { 37: try 38: { 39: transaction.Rollback(); 40: } 41: catch 42: { 43: } 44: } 45: throw; 46: } 47: finally 48: { 49: this.provider.Transaction = null; 50: if (flag) 51: { 52: this.provider.Connection.Close(); 53: } 54: } 55: } 56: new ChangeProcessor(this.services, this).SubmitChanges(failureMode); 57: this.AcceptChanges(); 58: } 59: finally 60: { 61: this.isInSubmitChanges = false; 62: } 63: }?
總結
以上是生活随笔為你收集整理的Linq to Sql : 三种事务处理方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 按GDP计算,美国的经济规模比我国高,如
- 下一篇: 多标签文本分类数据集_标签感知的文档表示