本來早就準備總結一下關于Repository、IUnitOfWork之間的聯系以及在各層中的分布,直到看到田園里的蟋蟀發表的文章:《DDD 領域驅動設計-談談 Repository、IUnitOfWork 和 IDbContext 的實踐》,才覺得有必要發表一下我個人的觀點及其相關的實現代碼,當然我的觀點不一定就比他們的好,我只是表達個人觀點而矣,大家勿噴。
關于Repository可以看看DUDU的這篇文章:關于Repository模式,我結合實際應用總結其核心概念為:Repository是受領域驅動及基于領域的意圖對外(領域服務、領域實體、應用服務層)提供管理實體的服務,本身不對數據的持久化負責,也不應該出現未受領域約束的方法。
關于Unit Of Work,我認為它的作用是:管理數據持久化的問題,并不受外界影響(比如:并發)確保在同一個工作單元(或者說是同一個業務領域)下操作的一致性(即:要么都成功,要么就都失敗),類似事務;
Entity Framework的DbContext其實就實現了Unit Of Work的功能(比如:SET用來查詢數據,同時通過SET的Add及Remove向DbContext注冊增加及刪除的請求服務,對于更新則是通過自動適時追蹤來實現的,只有通過SaveChanges才將實體的狀態執行化到數據庫中),于是關于在使用Entity Framework后有沒有必要再實現Unit Of Work,博客園的大牛們都有過爭論,我覺得應該依項目的實際情況來定,如果項目簡單且不考慮更換其它數據庫以及單元測試的便利性,那么直接使用DbContext就OK了,但如果不是,那么就需要用到Unit Of Work+Repository來包裝隔離實際的持久化實現。
對于Repository、IUnitOfWork 在領域層和應用服務層之間的關聯與代碼分布,我采用如下圖方式:
下面就來分享我關于Repository、IUnitOfWork 實現代碼:
Exam.Domain.IUnitOfWork定義:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Exam.Domain { public interface IUnitOfWork { IQueryable<TAggregateRoot> Entities<TAggregateRoot>() where TAggregateRoot : class, IAggregateRoot; void RegisterNew<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, IAggregateRoot; void RegisterModified<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, IAggregateRoot; void RegisterDeleted<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, IAggregateRoot; //void RegisterClean(); void Commit(); } }
我這里將RegisterClean注釋掉原因是:我認為一旦注冊了相應的持久化請求,那說明實體的狀態已經被更改了,而此時你執行清除沒有任何意義,有人可能會說,不清除在執行提交時會持久化到數據庫中,我想說,你不提交就行了,因為UnitOfWork是一個工作單元,它的影響范圍應僅限在這個工作單元內,當然想法很美好,現實有點殘酷,所以為了應對可能出現的反悔的問題,我這里還是寫出來了只是注釋掉了,具體怎樣,我們接著往下看。
Exam.Domain.Repositories.IRepository定義:
public interface IRepository<TAggregateRoot> where TAggregateRoot:class,IAggregateRoot { void Add(TAggregateRoot entity); void Update(TAggregateRoot entity); void Delete(TAggregateRoot entity); TAggregateRoot Get(Guid id); IQueryable<TResult> Find<TResult>(Expression<Func<TAggregateRoot, bool>> whereExpr, Expression<Func<TAggregateRoot, TResult>> selectExpr); }
我這里定義的IRepository包括基本的查、增、改、刪,有人可能又會說,你不是說倉儲中不應對包括持久化嗎?注意這里只里的增、刪、改只是用來向UnitOfWork發出相應的持久化請求的。當然也可以去掉倉儲中的這些方法,僅保留查詢方法,而若需要持久化就去調用UnitOfWork的相應的方法,正如 田園里的蟋蟀 那篇博文實現的那樣,但我覺得UnitOfWork工作單元不應該去主動要求持久化,而是應該被動的接收倉儲的持久化請求。
Exam.Repositories.IDbContext定義:
public interface IDbContext { DbSet<TEntity> Set<TEntity>() where TEntity : class; DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class; Task<int> SaveChangesAsync(); }
Exam.Repositories.EfUnitOfWork定義:
public class EfUnitOfWork:IUnitOfWork { private readonly IDbContext context; public EfUnitOfWork(IDbContext context) //如果要啟用RegisterClean,則IDbContext必需還要繼承自IObjectContextAdapter { this.context = context; } public IQueryable<TAggregateRoot> Entities<TAggregateRoot>() where TAggregateRoot : class, IAggregateRoot { return context.Set<TAggregateRoot>(); } public void RegisterNew<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, IAggregateRoot { context.Set<TAggregateRoot>().Add(entity); } public void RegisterModified<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, IAggregateRoot { context.Entry(entity).State = EntityState.Modified; } public void RegisterDeleted<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, IAggregateRoot { context.Entry(entity).State = EntityState.Deleted; } //public void RegisterClean() //{ // var objectContext = ((IObjectContextAdapter)context).ObjectContext; // List<ObjectStateEntry> entries = new List<ObjectStateEntry>(); // var states = new[] { EntityState.Added, EntityState.Deleted, EntityState.Modified}; // foreach (var state in states) // { // entries.AddRange(objectContext.ObjectStateManager.GetObjectStateEntries(state)); // } // foreach (var item in entries) // { // objectContext.ObjectStateManager.ChangeObjectState(item.Entity, EntityState.Unchanged); // //objectContext.Detach(item.Entity);可直接用這句替換上句 // } //} async public void Commit() { await context.SaveChangesAsync(); } }
這里的RegisterClean依然注釋掉了,當然如果啟用,則IDbContext必需還要繼承自IObjectContextAdapter,因為清除方法中用到了它,我這里的清除是真正的清除所有上下文中緩存。即便這樣在某種情況下仍存在問題,比如:Repository向UnitOfWork注冊了相應的操作后,沒有執行清除操作,也沒有提交,就這樣又在其它的業務領域中用到了相關的實體并且操作還不一樣,這時就會出現問題,我能想到的解決辦法如下:
public class EfUnitOfWork2:IUnitOfWork { private readonly IDbContext context; private readonly Dictionary<Type,IAggregateRoot> registedNews; private readonly Dictionary<Type, IAggregateRoot> registedModified; private readonly Dictionary<Type, IAggregateRoot> registedDeleted; private void Register<TAggregateRoot>(Dictionary<Type, IAggregateRoot> registerContainer, TAggregateRoot entity) where TAggregateRoot : class, IAggregateRoot { if (registerContainer.Values.Count(t=>t.Id==entity.Id)<=0) { registerContainer.Add(typeof(TAggregateRoot), entity); } } public EfUnitOfWork2(IDbContext context) //如果要啟用RegisterClean,則IDbContext必需還要繼承自IObjectContextAdapter { this.context = context; registedNews = new Dictionary<Type, IAggregateRoot>(); registedModified = new Dictionary<Type, IAggregateRoot>(); registedDeleted = new Dictionary<Type, IAggregateRoot>(); } public IQueryable<TAggregateRoot> Entities<TAggregateRoot>() where TAggregateRoot : class, IAggregateRoot { return context.Set<TAggregateRoot>(); } public void RegisterNew<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, IAggregateRoot { Register(registedNews, entity); } public void RegisterModified<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, IAggregateRoot { Register(registedModified, entity); } public void RegisterDeleted<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, IAggregateRoot { Register(registedDeleted, entity); } async public void Commit() { foreach (var t in registedNews.Keys) { context.Set(t).Add(registedNews[t]); } foreach (var t in registedModified.Keys) { context.Entry(registedModified[t]).State = EntityState.Modified; } foreach (var t in registedDeleted.Keys) { context.Entry(registedDeleted[t]).State = EntityState.Deleted; } await context.SaveChangesAsync(); } }
注意這里用到了DbContext中的DbSet Set(Type entityType)方法,所以IDbContext需加上該方法定義就可以了,這樣上面說的問題就解決了。其實與這篇實現的方法類似:
http://www.cnblogs.com/GaoHuhu/p/3443145.html
Exam.Repositories.Repository的定義:
public abstract class Repository<TAggregateRoot> : IRepository<TAggregateRoot> where TAggregateRoot : class,IAggregateRoot { private readonly IUnitOfWork unitOfWork; public Repository(IUnitOfWork uow) { unitOfWork = uow; } public void Add(TAggregateRoot entity) { unitOfWork.RegisterNew(entity); } public void Update(TAggregateRoot entity) { unitOfWork.RegisterModified(entity); } public void Delete(TAggregateRoot entity) { unitOfWork.RegisterDeleted(entity); } public TAggregateRoot Get(Guid id) { return unitOfWork.Entities<TAggregateRoot>().FirstOrDefault(t => t.Id == id); } public IQueryable<TResult> Find<TResult>(Expression<Func<TAggregateRoot, bool>> whereExpr, Expression<Func<TAggregateRoot, TResult>> selectExpr) { return unitOfWork.Entities<TAggregateRoot>().Where(whereExpr).Select(selectExpr); } }
這是一個通用的Repository抽象類,其它所有的倉儲在繼承該類的基礎上實現它自己的方法,目的是為了減輕重復代碼,順便看一下,我定義的接口中相關的持久化操作均用到了TAggregateRoot,表示聚合根,所以的操作均應以聚合根來進行,這里DDD里面的約束,我剛開始也有些不解,但仔細一想,是有道理的,我們舉個例子說明一下:
訂單與訂單項,訂單應為聚合根,訂單項應為實體或值對象,為什么這么說呢?
1.先有訂單存在,才會有訂單項;
2.訂單項不允許單獨自行刪除,若要刪除需通過訂單來執行,一般要么訂單創建,要么訂單刪除,不存在訂單生成后,還要去刪除訂單項的,比如:京東的訂單,你去看看生成訂單后,還能否在不改變訂單的情況下刪除訂單中的某個物品的。
3.訂單查詢出來了,相應的訂單項也就知道了,不存在只知道訂單項,而不知道訂單的情況。
描述的可能還不夠準確,但綜上所述基本可以確定聚合關系,而且若使用了EF,它的自動跟蹤與延遲加載特性也會為實現聚合根帶來方便,當然了也可以自行實現類似EF的自動跟蹤與延遲加載功能,已經有人實現了類似功能,可以看netfocus相關文章。
下面是演示示例:
//Unity IOC容器,我這里是演示直接寫代碼,其實更好的建議是通過配置注冊類型映射 var container = new UnityContainer(); container.RegisterType<IDbContext, ExamDbConext>(new ContainerControlledLifetimeManager()); container.RegisterType<IUnitOfWork, EfUnitOfWork>(); //container.RegisterType<IOrderRepository, OrderRepository>(); var unitofWork = container.Resolve<IUnitOfWork>(); //var orderRepository = container.Resolve<IOrderRepository>(); var orderRepository = new OrderRepository(unitofWork); //增加 orderRepository.Add(new Order() { OrderNo = "SO20151016", CreateDatetime = DateTime.Now, Status = "New", OrderItems = new[] { new OrderItem(){ ProductName="CPU", Description="CPU規格描述"}, new OrderItem(){ ProductName="HDD", Description="HDD規格描述"}, new OrderItem(){ ProductName="MB", Description="MB規格描述"}, new OrderItem(){ ProductName="KB", Description="KB規格描述"}, } }); unitofWork.Commit(); //更改 var order=orderRepository.Find(t => true, t => t).First(); order.OrderItems.Clear(); //清除所有子項 orderRepository.Update(order);//其實利用EF自動跟蹤狀態,如果在EF上下文中可以不用調用這句 unitofWork.Commit(); //刪除 orderRepository.Delete(order); unitofWork.Commit();
文章列表