開篇
什么是事務,事務的應用場景
做項目時,經常會遇到一些需求,比如注冊用戶時,要求同時存入用戶的基本信息和初始化該用戶的帳戶,如果在這兩個環節中的任何一個地方出錯,則要求回滾所有操作,這就是事務,它的主要目的是為了數據的完整性,即要么全盤成功,要么全盤失敗,大家都穿一條褲衩。
今天我要說的復雜事務
事務的概念很簡單,應用起來也很方便,當然可以在存儲過程中直接實現事務保證數據的完整性,但是我一般不會這樣做,因為我基本把數據庫就當做一個數據倉庫而已,它是一個容器,沒有其他邏輯,邏輯都放領域層來處理,因為這不是我們今天的主要話題,所以就不多講了。回到正題,那么我們要在C#中實現事務是怎么實現的呢,很簡單,直接調用Connection的BeginTransaction方法即可,我們拿ADO.Net中的SqlServer舉例,完整的應用示例如下:
以上代碼兩Usr表中插入兩條數據,要求這兩條數據必須都成功插入,否則回滾,邏輯很簡單。
更好的方式
上面的代碼沒什么問題,但是如果我們每個地方都這樣寫,那的確很繁瑣,所以出來了很多ORM框架來輔助我們,再看一下EntityFramework是怎么實現事務的
代碼:
using (RetailEntities context = new RetailEntities()) { context.Customers.Add(entity1); context.Customers.Add(entity2); context.SaveChanges(); }
估計大家都用過,沒錯,它在SaveChanges方法之前,自動為我們開始了一個事務。這樣做的好處,不言而喻,比起上面那樣做可謂是簡潔了許多。但是今天我們不使用EntityFramework,而是使用自己的方式來解決將要面臨的問題。
我實現事務的方式
看過我博客的朋友有可能會知道,我以前也寫過一個ORM框架(XDBFramework),今天我們主要不講這個框架,但會引用其中的代碼來說明問題,下面就上代碼,來看在XDBFramework中是怎樣實現事務的。
DataContext類
DataAccess類,也就是上面的_da
生成的DataContext類
調用代碼:

using (var context = new DataContext()) { long count = context.Admin.Count(); var a = DataContextStatic.Recharge.Count(s => s.State == Convert.ToInt32(1)); string passport = "x" + DateTime.Now.Ticks; context.BeginTransaction(); try { context.Admin.Insert(new Model_Admin { Passport = passport, Password = "123456", AddTime = DateTime.Now }); context.Admin.Insert(new Model_Admin { Passport = passport + "_2", Password = "123456", AddTime = DateTime.Now }); context.CommitTransaction(); } catch { context.RollbackTransaction(); } }
能這樣實現的關鍵在於DataContext對象擁有一個DataAccess對象,調用各個DataContext上的各個實體操作自身對應表的時候會調用同一個DataAccess對象,也就是同一個connection,同一個trasaction進行數據的檢索,插入,更新,刪除操作。這樣就實現了把多個操作放到一個事務內。
引出問題
想象一下這樣一種場景,用戶在購買一件商品時,既要生成訂單,又要扣除其帳戶的相應金額,我們假定在生成訂單時會生成多個明細,扣除余額時也會有多條明細。
解決辦法:
1.有一種方式就是建立一個manager類,然后把生成訂單和扣除其帳戶的相應金額的邏輯寫到一個事務放到這個manager中,如果采用這種方式,當然就不會有本文存在了。
2.這里要以領域模型的思維方式,即什么對象做什么事,以領域模型建模,這里就有兩個模型,即用戶帳戶和訂單。
於是:
a.訂單實體里有生成訂單的方法,這個方法中包含插入訂單總覽和明細。
b.帳戶這個實體里有扣除余額的方法,這個方法中包含扣除余額和扣除明細
這樣一來,就有好幾個需要事務,即:
1,生成訂單和扣除余額
2,生成訂單和明細
3,扣除余額和扣除明細
這樣問題就來了,它們本身是一個事務,為了模型的完整,我們現在將它們放在了三處。如何在最后將它們合並成一個事務呢,這就是我們今天要解決的問題。
進入實例
場景
工作流大家應該接觸過,我現在就拿這個來說,在保存設計圖時,我需要保存那些節點和線條的位置和大小等,在這之前如果是一副被修改的流程圖我還要清除原來的元素,在這個場景中,有很多操作都要求在一個事務中,比如之前元素的清徐,新節點的保存,新連接線的保存,新注釋的保存等。而這里又有很多實體,比如流程圖模版,活動節點(用戶活動,系統活動),連接線,注釋等,拿它們的保存方法來講都有不同邏輯,甚至在保存方法內也還有事務的存在。
代碼
流程圖的保存方法
可以看到這里開始了一個大的事務,將各元素的清除和保存都放到了這個事務中。
以下的刪除方法中又包含了事務(以"Transaction(()=>)"開始的地方)
實現
總結
主要思想是為每個線程分配一個DataContext對象,如果發現是同一個線程在開啟事務時,判斷是否已經開啟事務,如是則直接使用已經開啟的事務,如否則開啟一個新的事務,原理很簡單。