本來早就准備總結一下關於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();
