本来早就准备总结一下关于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();

  

关于Repository、IUnitOfWork 在领域层和应用服务层之间的代码分布与实现的更多相关文章

  1. ABP学习入门系列(三) (领域层中的仓储Repository)

    一,仓储定义:“在领域层和数据映射层的中介,使用类似集合的接口来存取领域对象”(Martin Fowler) . 仓储用来操作数据库进行数据存取.仓储接口在领域层定义,而仓储的实现类应该写在基础设施层 ...

  2. ABP领域层

    1.实体Entites 1.1 概念 实体是DDD(领域驱动设计)的核心概念之一. 实体是具有唯一标识的ID且存储在数据库总.实体通常被映射成数据库中的一个表. 在ABP中,实体继承自Entity类. ...

  3. ABP(现代ASP.NET样板开发框架)系列之11、ABP领域层——仓储(Repositories)

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之11.ABP领域层——仓储(Repositories) ABP是“ASP.NET Boilerplate Proj ...

  4. ABP(现代ASP.NET样板开发框架)系列之12、ABP领域层——工作单元(Unit Of work)

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之12.ABP领域层——工作单元(Unit Of work) ABP是“ASP.NET Boilerplate Pr ...

  5. ABP入门系列(4)——领域层定义仓储并实现

    一.先来介绍下仓储 仓储(Repository): 仓储用来操作数据库进行数据存取.仓储接口在领域层定义,而仓储的实现类应该写在基础设施层. 在ABP中,仓储类要实现IRepository接口,接口定 ...

  6. ABP入门系列(3)——领域层创建实体

    这一节我们主要和领域层打交道.首先我们要对ABP的体系结构以及从模板创建的解决方案进行一一对应.网上有代码生成器去简化我们这一步的任务,但是不建议初学者去使用. 一.首先来看看ABP体系结构 领域层就 ...

  7. ABP领域层——工作单元(Unit Of work)

    ABP领域层——工作单元(Unit Of work) 点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之12.ABP领域层——工作单元(Unit Of work) ...

  8. ABP领域层——仓储(Repositories)

    ABP领域层——仓储(Repositories) 点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之11.ABP领域层——仓储(Repositories) ABP是 ...

  9. ABP入门系列(2)——领域层创建实体

    ABP入门系列目录--学习Abp框架之实操演练 这一节我们主要和领域层打交道.首先我们要对ABP的体系结构以及从模板创建的解决方案进行一一对应.网上有代码生成器去简化我们这一步的任务,但是不建议初学者 ...

随机推荐

  1. MySQL慢查询日志总结

    慢查询日志概念 MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志 ...

  2. BIT 树状数组 详解 及 例题

    (一)树状数组的概念 如果给定一个数组,要你求里面所有数的和,一般都会想到累加.但是当那个数组很大的时候,累加就显得太耗时了,时间复杂度为O(n),并且采用累加的方法还有一个局限,那就是,当修改掉数组 ...

  3. Ubuntu下安装mod_python报错(GIT错误)

    Ubuntu下安装mod_python3.4.1版本报出如下错误: writing byte-compilation script '/tmp/tmpE91VXZ.py' /usr/bin/pytho ...

  4. laravel中如何防止直接访问.env文件

    .env文件含有数据库账号密码等敏感数据,在laravel5.2中,在本地访问127.0.0.1/laravel/.env可直接访问到.env. 为避免.env被直接访问,可使用重定向,方法如下: 在 ...

  5. Des与3Des加密解密

    /// <summary> /// Des和3Des算法 /// </summary> public class Des { /// <summary> /// D ...

  6. linux进程管理(上)

    程序和进程的区别: 1.程序是一种静态资源 程序启动产生进程 2.程序与进程无一一对应原则  进程是动态的一个过程 父进程和子进程在前面提过 前台进程:执行命令时只能等待的进程为前台进程也叫异步进程 ...

  7. 对 griview获取的数据添加方法 6月

    <asp:TemplateField HeaderText="日期">                            <HeaderStyle CssCl ...

  8. Android中SQLite数据库小计

    2016-03-16 Android数据库支持 本文节选并翻译<Enterprise Android - Programing Android Database Applications for ...

  9. Windows Azure 服务总线和物联网

    机器到机器 (M2M) 计算正迅速成为一种技术,所有开发人员和架构师需要拥抱. 许多研究表明一个未来世界的数百亿美元的设备 (在地球上的每一个人的出现).MSDN杂志有2篇文章讨论Azure服务总线和 ...

  10. 移动Web触控事件总结

    移动web风风火火几多年,让我这个在Pc端漂流的前端er不免心生仰慕,的确入行几多年,也该是时候进军移动web了.移动web中踩到的第一个坑就是事件问题,所以在吸取众大神的经验后,特作总结以示后来者. ...