概念
Unit of Work 即工作單元。 用來維護一組受業務影響的對象列表,將多個操作放在一個單元中,把操作原子化,通過事務統一完成一次提交,如果某個過程出現異常,就將所有修改進行回滾,保證數據的有效狀態。同時減少了應用程序與數據庫通信,有利於提升系統的性能。
具體使用(銀行領域的轉賬建模)
整體項目結構預覽
構建UnitOfWork.Infrastructure
1、新建Domain文件夾,添加IAggregateRoot接口
IAggregateRoot接口屬於聚合根,所有業務對象(Entity)都需要實現聚合根。外部對象需要訪問聚合內的實體時,只能通過聚合根進行訪問,而不能直接訪問。領域模型需要根據領域概念分成多個聚合,每個聚合都有一個實體作為聚合根,通俗的說,領域對象從無到有的創建,以及CRUD的操作都應該作用於聚合根上,而不是單獨的某個實體。
2、新建UnitofWork文件夾,添加IUnitofWorkRepository接口
void PersistCreationOf(IAggregateRoot entity); void PersistUpdateOf(IAggregateRoot entity); void PersistDeletionOf(IAggregateRoot entity);3、添加IUnitofWork接口 注意在添加/修改/刪除的方法中傳入IUnitofWorkRepository,在提交時Unit Of Work將真正持久化工作放在IUnitofWorkRepository的具體類實現中進行
void RegisterUpdate(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository); void RegisterAdd(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository); void RegisterRemove(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository) void Commit();
構建UnitOfWork.Model
1、新建Account類並實現IAggregateRoot接口 a.定義公共的屬性Balace(余額) b.定義帶參數(balace) 的構造函數
2、添加IAccountRepository接口,將Account持久化 定義添加/更新/刪除方法
3、新建AccoutService類 定義公共的IAccountRepository對象和IUnitOfWork對象,通過其構造器實現依賴注入
public IAccountRepository _accountRepository; public IUnitOfWork _unitOfWork; public AccoutService(IAccountRepository accountRepository, IUnitOfWork unitOfWork) { _accountRepository = accountRepository; _unitOfWork = unitOfWork; } /// <summary> /// 轉賬 /// </summary> /// <param name="from"></param> /// <param name="to"></param> /// <param name="account"></param> public void Transfer(Account from ,Account to ,decimal balace) { if (from.Balace > balace) { from.Balace -= balace; to.Balace += balace; _accountRepository.Save(from); _accountRepository.Save(to); _unitOfWork.Commit(); } }
構造UnitOfWork.Repository
1、添加AccountRepository類,AccountRepository實現了IAccountRepository和IUnitofWorkRepository接口,IAccountRepository方法的實現簡單地將工作委托至Unit Of Work(傳入待持久化實體以及Repository的引用),IUnitofWorkRepository方法中實現了真正的持久化任務,具體的持久化方式可以用ADO.NET/EF/NH等。
public class AccountRepository:IAccountRepository ,IUnitOfWorkRepository { private IUnitOfWork _unitOfWork; public AccountRepository(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public void Save(Account account) { _unitOfWork.RegisterAdd(account,this); } public void Update(Account account) { _unitOfWork.RegisterUpdate(account,this); } public void Remove(Account account) { _unitOfWork.RegisterRemove(account,this); } public void PersistCreationOf(Infrastructure.Domain.IAggregateRoot entity) { //ADO.NET/EF/NH } public void PersistUpdateOf(Infrastructure.Domain.IAggregateRoot entity) { //ADO.NET/EF/NH } public void PersistDeletionOf(Infrastructure.Domain.IAggregateRoot entity) { //ADO.NET/EF/NH } }2、添加NHUnitOfWork類,實現IUnitofWork接口
public class NHUnitOfWork : IUnitOfWork { public Dictionary<IAggregateRoot, IUnitOfWorkRepository> addedEntitys; public Dictionary<IAggregateRoot, IUnitOfWorkRepository> changedEntitys; public Dictionary<IAggregateRoot, IUnitOfWorkRepository> deletedEntitys; public NHUnitOfWork() { addedEntitys = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>(); changedEntitys = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>(); deletedEntitys = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>(); } public void RegisterUpdate(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository) { if (!changedEntitys.ContainsKey(entity)) { changedEntitys.Add(entity,unitOfWorkRepository); } } public void RegisterAdd(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository) { if (!addedEntitys.ContainsKey(entity)) { addedEntitys.Add(entity,unitOfWorkRepository); } } public void RegisterRemove(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository) { if (!deletedEntitys.ContainsKey(entity)) { deletedEntitys.Add(entity,unitOfWorkRepository); } } public void Commit() { using (TransactionScope scope = new TransactionScope()) { foreach (IAggregateRoot entity in this.addedEntitys.Keys) { this.addedEntitys[entity].PersistCreationOf(entity); } foreach (IAggregateRoot entity in this.changedEntitys.Keys) { this.changedEntitys[entity].PersistUpdateOf(entity); } foreach (IAggregateRoot entity in this.deletedEntitys.Keys) { this.deletedEntitys[entity].PersistDeletionOf(entity); } scope.Complete(); } } }NHUnitOfWork類使用3個字典變量來記錄對業務實體的代執行修改。addedEntitys 對應於被添加到數據存儲的實體,changedEntitys 處理更新的實體,deletedEntitys處理實體刪除,與字典中的實體鍵匹配的IUnitOfWorkRepository將被保存下來,並用於 Commit 方法之中 ,來調用Repository對象,該對象包含真正持久化實體的代碼 。Commit方法遍歷每一個字典,並調用相應的IUnitOfWorkRepository方法(傳遞實體引用)。Commit方法中的工作均被 TransactionScope 代碼包裝起來,如果在IUnitOfWorkRepository中執行任務時出現異常,則所有工作回滾,數據存儲將保持原來的狀態。
構建UnitOfWork.Console
1、添加控制台項目用於驗證UnitofWork模式效果
static void Main(string[] args) { Account a = new Account(1000); System.Console.WriteLine("現在a,存有{0}",a.Balace); Account b = new Account(200); System.Console.WriteLine("現在b,存有{0}",b.Balace); System.Console.WriteLine("a轉給b 500元,開始轉賬......"); IUnitOfWork unitOfWork = new NHUnitOfWork(); IAccountRepository accountRepository = new AccountRepository(unitOfWork); AccoutService service = new AccoutService(accountRepository,unitOfWork); service.Transfer(a,b,500); System.Console.WriteLine("轉賬結束"); System.Console.WriteLine("a當前金額:{0}",a.Balace); System.Console.WriteLine("b當前金額:{0}",b.Balace); System.Console.ReadKey(); }執行后結果如下:
![]()
UML類圖
總結
對於Unit of Work模式是一種解決方案,一種思路,每個項目的環境都是不一樣的,所以我們需要理解的就是其中的原理。以上實例主要是講解如何部署UnitofWork模式,便於大家的理解,謝謝!
疑問討論
上面講的是網上資料中最普遍的說法,對經典的Unit of Work模式進行了說明。但是我個人有一個觀點需要和大家探討一下:
先說一個問題,大家在初次接觸Unit of Work的時候,會不會有一個疑問——這個和數據庫事務有什么區別?
然后我再說我的觀點吧。
1.我覺得Unit of Work就是一個思想,工作單元及原子性操作,而這個關聯到數據庫中,也就是事務了,所以我覺得數據庫事務是Unit of Work在數據庫中的一種體現。
2.上面講述的Unit of Work是網上最常見的一種說法。我個人覺得,上面那只是一種實現方式,而在網上看到大部分關於Unit of Work的優點中“減少數據庫連接、減少數據庫連接時間”,這些都是這種實現方式的優點。但是我認為這並不是Unit of Work真正的意圖,我認為Unit of Work只是單純的原子操作思想,就像我認為微軟提供的TransactionScope也是Unit of Work的一種實現一樣。
3.我認為上述的這種普遍的說法可能存在這樣的問題:
前置條件:數據庫表主鍵為自增的int型ID。
場景: A表一條數據執行了修改操作,B表執行了一個新增操作,接下來有一個if判斷,一條分支是拿B表新增數據的ID調用一個webapi,在之后還有一個對C表的修改操作,另一條分支是在D表中新增一條數據。
問題:因為所有的增刪改操作都會被延后到工作單元結束的地方進行執行,所以在代碼執行到往B表插入數據的時候,實際上沒有插入,這時如果在if判斷的地方走了第一個分支,用B表插入的數據ID去調用webapi,這將會出現問題。
解決方案:
1.不適用自增ID,比如換成GUID;
2.為webapi實現一個上述的Unit of Work的適配器,將api操作也壓入集合中,等到后面一起處理;
3.在工作單元開始的時候就開啟一個數據庫事務。
第一個解決方案在一些成型系統中再遇到就是不可行的,第二個方案會稍顯麻煩,第三個方案就不存在之前說的“減少數據庫連接時間”的優點了。
以上是我個人對Unit of Work的觀點與疑問,希望大家能夠各發己見,一起探討一下。