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

简介:

本来早就准备总结一下关于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定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
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定义:

1
2
3
4
5
6
7
8
9
10
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定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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注册了相应的操作后,没有执行清除操作,也没有提交,就这样又在其它的业务领域中用到了相关的实体并且操作还不一样,这时就会出现问题,我能想到的解决办法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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  in  registedNews.Keys)
         {
             context.Set(t).Add(registedNews[t]);
         }
 
         foreach  ( var  in  registedModified.Keys)
         {
             context.Entry(registedModified[t]).State = EntityState.Modified;
         }
 
         foreach  ( var  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的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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相关文章。

下面是演示示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//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();

  本文转自 梦在旅途 博客园博客,原文链接:http://www.cnblogs.com/zuowj/p/4884947.html  ,如需转载请自行联系原作者


相关文章
|
JavaScript
VUE element-ui 之slot-scope=“scope“常见报错解决方法
VUE element-ui 之slot-scope=“scope“常见报错解决方法
1523 0
VUE element-ui 之slot-scope=“scope“常见报错解决方法
|
前端开发 JavaScript
使用Element-UI中的el-upload实现文件的上传demo(亲测有用)
使用Element-UI中的el-upload实现文件的上传demo(亲测有用)
|
SQL 关系型数据库 MySQL
MySQL Workbench的安装与配置
MySQL Workbench的安装与配置
|
10月前
|
Java Linux Docker
什么是 Docker?如何将 Spring Boot 应用程序部署到 Docker?
什么是 Docker?如何将 Spring Boot 应用程序部署到 Docker?
308 3
|
Ubuntu 持续交付 API
如何使用 dotnet pack 打包 .NET 跨平台程序集?
`dotnet pack` 是 .NET Core 的 NuGet 包打包工具,用于将代码打包成 NuGet 包。通过命令 `dotnet pack` 可生成 `.nupkg` 文件。使用 `--include-symbols` 和 `--include-source` 选项可分别创建包含调试符号和源文件的包。默认情况下,`dotnet pack` 会先构建项目,可通过 `--no-build` 跳过构建。此外,还可以使用 `--output` 指定输出目录、`-c` 设置配置等。示例展示了创建类库项目并打包的过程。更多详情及命令选项,请参考官方文档。
650 13
|
JavaScript
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
这篇文章解释了为什么在Vue中组件的`data`属性必须是一个函数而不是一个对象。原因在于组件可能会有多个实例,如果`data`是一个对象,那么这些实例将会共享同一个`data`对象,导致数据污染。而当`data`是一个函数时,每次创建组件实例都会返回一个新的`data`对象,从而确保了数据的隔离。文章通过示例和源码分析,展示了Vue初始化`data`的过程和组件选项合并的原理,最终得出结论:根实例的`data`可以是对象或函数,而组件实例的`data`必须为函数。
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
|
监控 Linux 数据库连接
手把手教你从本地到云端:全面解析Blazor应用的部署流程与最佳实践,助你轻松掌握发布Blazor WebAssembly应用到Azure的每一个细节
【8月更文挑战第31天】本文详细介绍了将 Blazor 应用从本地部署到 Azure 的全过程。首先确保已在 Visual Studio 中创建 Blazor WebAssembly 应用,接着清理项目并配置发布选项。然后在 Azure 中创建 App Service 并完成应用部署。最后,配置环境变量、SSL 和监控,确保应用稳定运行。附带示例代码,展示如何加载和使用 Azure 环境变量。通过最佳实践指导,帮助你顺利完成 Blazor 应用的云端部署。
453 0
|
设计模式 测试技术 数据库连接
Entity Framework Core 中的依赖注入超厉害!DI 与 DbContext 完美结合,提升开发效率
【8月更文挑战第31天】依赖注入(DI)是一种软件设计模式,用于将对象的依赖关系与其创建过程解耦,从而提升代码的可测试性、可维护性和可扩展性。在Entity Framework Core中使用DI能够提高可测试性,便于替换DbContext实现以进行单元测试;增强可维护性,使代码模块化并清晰展示组件间的依赖关系;提升可扩展性,方便添加新服务和功能而不需修改现有代码。通过Microsoft.Extensions.DependencyInjection等依赖注入容器,可将DbContext注册并注入到需要使用的类中,简化数据库管理和测试流程。
318 0
|
JavaScript 前端开发 UED
HTML 文件上传 ,学会了保证不亏!
HTML 文件上传 ,学会了保证不亏!
|
Linux API 数据库
Python 金融交易实用指南(三)(4)
Python 金融交易实用指南(三)
151 1