天天看點

Linq to Sql : 三種事務處理方式

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

    當調用SubmitChanges 時,L2S會檢查目前環境是否開啟了事務(下面兩節中建立的事務),如果沒有開始事務,則 L2S啟動本地事務(IDbTransaction),并使用此事務執行所生成的 SQL 指令。

    使用Reflector檢視DataContext.SubmitChanges的源代碼(見本文最後的附錄),可以看到,如果開啟了一個事務,并将其傳給DataContext.Transaction,則在執行SubmitChanges時就不會再重新開啟一個新的事務了。

    例如,下面的代碼會建立應隐式事務:

    可以打開SQL Server Profile來檢視生成的T-SQL,生成的Update語句被包含在Begin Transaction和Commit Transaction之間,如下圖所示:

Linq to Sql : 三種事務處理方式

    上圖是我使用Linq to SQL + SQL Server 2000進行測試的結果。下圖是我用Linq to SQL + SQL Server 2008測試的結果:

Linq to Sql : 三種事務處理方式

    很奇怪的是,居然沒有Begin Transaction和Commit Transaction了。Begin/commit trand的事件類型是“SQL:BatchStarting”/“SQL:BatchCompleted”,從這個圖中可以看到,我有跟蹤這個事件(譬如第一個框中的Select指令),汗……是MSDN上說錯了?

    抱着這個疑問,我又針對Linq to Sql + SQL Server 2008做了進一步測試(這個例子也可以用來測試後面兩節中的事務處理,確定所有操作被分封裝在同一個事務中。):

Linq to Sql : 三種事務處理方式

    這裡裡面做了兩件事:建立一個新對象,同時還修改原有記錄中的值。我故意Insert可以執行成功,而讓Update語句執行出錯;如果有啟用事務,則出錯後事務會復原,最終不會建立新記錄;如果沒有啟用事務,則可以正常插入,但不會執行第二步中的更新。

    注意:從理論上講,執行SubmitChanges時,并不一定是按照上面代碼的順序,先執行插入再執行更新;下面是MSDN上的說法:

    當您進行此調用時,DataContext 會設法将您所做的更改轉換為等效的 SQL 指令。您可以使用自己的自定義邏輯來重寫這些操作,但送出順序是由 DataContext 的一項稱作“更改處理器”的服務來協調的。事件的順序如下:

當您調用 SubmitChanges 時,LINQ to SQL 會檢查已知對象的集合以确定新執行個體是否已附加到它們。如果已附加,這些新執行個體将添加到被跟蹤對象的集合。

所有具有挂起更改的對象将按照它們之間的依賴關系排序成一個對象序列。如果一個對象的更改依賴于其他對象,則這個對象将排在其依賴項之後。

在即将傳輸任何實際更改時,LINQ to SQL 會啟動一個事務來封裝由各條指令組成的系列。

對對象的更改會逐個轉換為 SQL 指令,然後發送到伺服器。

    此時,如果資料庫檢測到任何錯誤,都會造成送出程序停止并引發異常。将復原對資料庫的所有更改,就像未進行過送出一樣。DataContext 仍具有所有更改的完整記錄。

    是以,這裡還是打開SQL Server Profile來确認:

Linq to Sql : 三種事務處理方式

    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,就沒轍了.......

    SubmitChanges隻能處理最基本的CUD(Create/Update/Delete)操作;而在日常的應用中,邏輯往往沒有這麼簡單,或者考慮性能等因素,我們需要配合ADO.Net執行原生的TSQL;這時我們可能要讓ADO.Net + Linq to SQL來進行配合處理。

    也就是說,我們可以手工建立一個DbConnection和DbTransaction,然後傳給DataContext,代碼示例如下:

    最後總結一下使用顯式本地事務的優缺點:

    優點:可以配合Ado.Net一起使用,或者跨DataContext使用,實作負責的業務邏輯。

    缺點:處理步驟比較繁瑣。L2S中的DataContext已經提供了ExcuteCommand方法來執行原生的TSQL,這裡還這樣使用Ado.net就太折騰自己了.......

    使用顯式可分發事務進行處理的示例代碼如下:

    或者這樣:

<b>    SqlConnection</b> 對象的 <b>ConnectionString</b> 屬性支援 <b>Enlist</b> 關鍵字,該關鍵字訓示 <b>System.Data.SqlClient</b> 是否檢測事務上下文并在分布式事務中自動登記連接配接。如果此關鍵字設定為 True(預設設定),則會在打開的線程的目前事務上下文中自動登記連接配接。如果此關鍵字設定為 False,則 SqlClient 連接配接不會與分布式事務互動。如果未在連接配接字元串中指定 <b>Enlist</b>,并且如果在打開相應連接配接時檢測到一個分布式事務,則會在此分布式事務中自動登記此連接配接。(FROM  Sql Server 2008 聯機叢書)

    關于TransactioExtension的封裝,代碼如下所示:(由于TransactionScope預設的事務隔離級别是IsolationLevel.Serializable,這裡調整為ReadCommitted隔離級别,以保持與Sql Server的預設隔離級别一緻):

    不過在使用TransactionScope時,需要注意,如果使用的資料庫是SQL Server 2000,或者需要實作跨多個資料庫進行事務控制,則需要開啟DTC服務(位于:開始-&gt;管理工具-&gt;服務-&gt;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) 會管理此類事務,并內建在這些事務中通路的所有資料總管。慶幸的是:在打開一個具有活動<b>TransactionScope</b>事務的連接配接而未打開任何其他連接配接的情況下,該事務會以輕型事務的形式送出,而不是産生完全分布式事務的額外開銷。

最後來個彙總:

事務類型

優點

缺點

隐式事務

使用簡單,由L2S自動處理。

僅限于單個DataContext中的SubmitChanges方法内使用。

顯式本地事務

可以配合Ado.Net一起使用,可以跨多個DataContext來使用

使用相對繁瑣一點兒;且不支援與DataContext.ExecuteCommand配合使用

顯式可分發事務

功能強大(可以配合ADO.Net或者DataContext.ExcuteCommand使用,可以跨DataContext使用,可以跨資料庫使用,可以跨伺服器使用),使用簡單

可能會對性能有一些影響(我暫時也沒有測試過-,-)

附:用Reflector檢視DataContext.SubmitChanges的源代碼如下: