做者:依樂祝html
今天在寫CzarCms的UnitOfWork的使用使用到了這個TransactionScope事務,所以對它進行了相關資料的查閱並記錄以下,但願對大夥在.NET Core中使用有所幫助。數據庫
您是否曾嘗試使用C#代碼來實現事務?一般,咱們在SQL中一次執行多個Insert / Update語句的話可能就會使用到事務。事務遵循ACID(原子性,一致性,隔離性,持久性)規則,這樣全部的語句要麼所有執行成功要麼所有被取消並執行回滾操做。 而咱們今天要講的TransactionScope則能夠容許咱們在應用程序級別實現這個過程。在某些狀況下,您可能須要在同一個數據庫甚至多個數據庫(分佈式事務)中執行不一樣的操做,或者因爲某些其餘約束,它沒法在數據庫級別來完成,或者應用程序的開發人員對數據庫的接觸較少,那麼這時候TransactionScope將會讓你遊刃有餘。c#
TransactionScope做爲System.Transactions的一部分被引入到.NET 2.0。同時SqlClient for .NET Core 從 2.1 及以上版本開始提供對System.Transactions的支持 。 它是一個類,它提供了一種簡單的方法,能夠將一組操做做爲事務的一部分來進行處理,而沒必要擔憂場景背後的複雜性。若是某個操做在執行的過程當中失敗的話,則整個事務將失敗並執行回滾操做,從而撤消已完成的全部操做。全部這些都將由框架處理,從而確保數據的一致性。windows
要使用它,您須要添加System.Transactions的引用,若是你使用的是.net core的話。這個引用被包含在netcoreapp2.2\System.Transactions.Local.dll
中, 該引用是框架庫的一部分(一般默認狀況下不會自動添加)。添加後,在咱們想要使用它的地方添加名稱空間 System.Transactions便可。代碼以下所示:服務器
try { using (TransactionScope scope = new TransactionScope()) { // Do Operation 1 // Do Operation 2 //... // 若是全部的操做都執行成功,則Complete()會被調用來提交事務 // 若是發生異常,則不會調用它並回滾事務 scope.Complete(); } } catch (ThreadAbortException ex) { // 處理異常 }
在上面的代碼中咱們能夠看到咱們在建立TransactionScope實例時使用了using
語句塊及Disposable塊,它確保了當dispose離開塊並結束事務範圍時調用dispose來進行資源的釋放。
在一個Transaction範圍中,咱們能夠作多個鏈接甚至連接到不一樣數據庫的操做的,以下所示:網絡
using (TransactionScope scope = new TransactionScope()) { using (con = new SqlConnection(conString1)) { con.Open(); // 執行操做 1 // 執行操做 2 //... } using (con = new SqlConnection(conString2)) { con.Open(); // 執行操做 1 // 執行操做 2 //... } scope.Complete(); }
下面咱們使用兩個不一樣的數據庫鏈接字符串來鏈接不一樣的數據庫。固然咱們也能夠根據咱們的業務要求使用盡量多數據庫。咱們也能夠再事務中嵌套事務。以下代碼所示:app
public void DoMultipleTransaction() { try { using (TransactionScope scope = new TransactionScope()) { using (con = new SqlConnection(conString1)) { con.Open(); // 執行操做1 } OtherTransaction(); scope.Complete(); } } catch (ThreadAbortException ex) { // 處理異常 } } private void OtherTransaction() { using (TransactionScope scope = new TransactionScope()) { using (con = new SqlConnection(conString2)) { con.Open(); // 執行操做 } scope.Complete(); } }
這裏最頂層的事務範圍稱爲根範圍。另外這裏須要注意的是即便經過調用scope.Complete()完成內部事務(上面的OtherTransaction ),若是因爲各類緣由沒法調用rootscope complete,那麼整個事務也將被回滾包括內部的事務。框架
*注意:執行分佈式trsanctions時,您可能會收到如下異常之一*異步
這兩個錯誤都是因爲一樣的緣由,第一個是在數據庫和應用程序是同一個服務器時發生的,而在另外一個則是服務跟數據庫分別部署在兩臺服務器上。對於同一臺服務器,請轉到run-> cmd-> services.msc。運行名爲Distributed Transaction Coordinator的服務並自動啓動啓動類型,以便在系統從新啓動時再次啓動它。對於2,你可能須要參照這個連接的內容進行相應的設置
TransactionScope 類提供了多個重載構造函數,它們接受 TransactionScopeOption 類型的枚舉,而該枚舉定義事務範圍行爲。
TransactionScope對象有如下三個選項:
若是用 Required] 實例化範圍而且存在環境事務,則該範圍會聯接該事務。 相反,若是不存在環境事務,該範圍就會建立新的事務併成爲根範圍。 這是默認值。 在使用 Required時,不管範圍是根範圍仍是僅聯接環境事務,該範圍中的代碼都不須要有不一樣的行爲。 該代碼在這兩種狀況下的行爲應相同。
若是用 RequiresNew 實例化範圍,則它始終爲根範圍。 它會啓動一個新事務,而且其事務成爲該範圍中的新環境事務。
若是用 Suppress 實例化範圍,則不管是否存在環境事務,範圍都從不參與事務。 始終使用此值實例化的做用域具備null做爲其環境事務。
下面來讓咱們看一組實例代碼:
using (TransactionScope scope = new TransactionScope()) { // 聯接環境事務,或者在環境事務不存在的狀況下建立新的環境事務。 using (TransactionScope scope1 = new TransactionScope(TransactionScopeOption.Required)) { // Do Operation scope1.Complete(); } //成爲新的根範圍,也就是說,啓動一個新事務並使該事務成爲其本身範圍中的新環境事務。 using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.RequiresNew)) { // Do Operation scope2.Complete(); } //根本不參與事務。 所以沒有環境事務。 using (TransactionScope scope3 = new TransactionScope(TransactionScopeOption.Suppress)) { // Do Operation scope3.Complete(); } scope.Complet
在這裏,咱們使用不一樣的TransactionScopeOptions在父事務下建立了三個事務。默認狀況下,範圍是required ,這裏父事務就是採用的這個默認參數進行建立的。它是一個建立新事務的根範圍,並將其標記爲環境事務。scope1也是使用required建立的,由於咱們已經有了一個環境事務(範圍),因此它加入到父事務中。scope2是使用RequiresNew選項建立的,這意味着它是一個獨立於環境事務處理的新事務。scope3是用suppress建立的選項,這意味着它不參與任何環境事務。不管環境事務是否成功執行,它都會被執行。父(全局)範圍完成後,將提交全部環境事務。
EF Core 依賴數據庫提供程序以實現對 System.Transactions 的支持。 雖然支持在 .NET Framework 的 ADO.NET 提供程序之間十分常見,但最近纔將 API 添加到 .NET Core,所以支持並未獲得普遍應用。 若是提供程序未實現對 System.Transactions 的支持,則可能會徹底忽略對這些 API 的調用。 SqlClient for .NET Core 從 2.1 及以上版本開始支持 System.Transactions。若是嘗試在低版本中 如.NET Core 2.0中嘗試使用該功能將引起異常。
自版本 2.1 起,.NET Core 中的 System.Transactions 實現將不包括對分佈式事務的支持,所以不能使用 TransactionScope
或 CommittableTransaction
來跨多個資源管理器協調事務。主要是不依賴windows中的mstsc功能。
異步方法使用時須要注意:
在下面的例子中,咱們在TransactionScope
內部使用await
。
using(var scope = new TransactionScope()) { var groups = await Context.ProductGroups.ToListAsync()。ConfigureAwait(false); }
看起來沒有問題,但它會拋出一個 System.InvalidOperationException:``A TransactionScope must be disposed on the same thread that it was created.
緣由是默認狀況下TransactionScope
不會從一個線程切換到另外一個線程。爲了解決這個問題,咱們必須使用 TransactionScopeAsyncFlowOption.Enabled
:
using(var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { var groups = await Context.ProductGroups.ToListAsync()。ConfigureAwait(false); }
如今應該能夠了吧?這取決於下面的狀況。
若是咱們使用和不使用TransactionScopeAsyncFlowOption這個
選項的時候都使用了相同的數據庫鏈接,而且第一次執行的時候沒有使用這個選項,那麼咱們會獲得另外一個異常: System.InvalidOperationException:``Connection currently has transaction enlisted. Finish current transaction and retry.
換句話說,因爲第一個訪問的緣由,第二個會話將會失敗。以下代碼所示:
try { using (var scope = new TransactionScope()) { // We know this one - System.InvalidOperationException: // TransactionScope必須放在與建立它相同的線程上。 var groups = await Context.ProductGroups.ToListAsync().ConfigureAwait(false); } } catch (Exception e) { // error handling } using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { // Implemented correctly but throws anyways // System.InvalidOperationException: // 當前鏈接已經被記錄。完成當前事務並重試。 var groups = await Context.ProductGroups.ToListAsync().ConfigureAwait(false); }
想象一下,若是第一個調用是在第三方庫或您正在使用的框架中完成的,二您不瞭解其中的代碼 - 若是您以前沒有看到此錯誤,那麼你講無從下手來解決這個問題。
本文帶着你們熟悉了一遍TransactionScope並對其使用進行了介紹!同時介紹了在.NET Core中使用TransactionScope的一些注意事項!但願對你們有所幫助。另附上.NET Core實戰項目交流羣:637326624