Repository、IUnitOfWork
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 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的定义:
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(); |
Repository、IUnitOfWork的更多相关文章
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(3)
上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(2)> 这篇文章主要是对 DDD.Sample 框架增加 Transa ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(2)
上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(1)> 阅读目录: 抽离 IRepository 并改造 Reposi ...
- 关于Repository、IUnitOfWork 在领域层和应用服务层之间的代码分布与实现
本来早就准备总结一下关于Repository.IUnitOfWork之间的联系以及在各层中的分布,直到看到田园里的蟋蟀发表的文章:<DDD 领域驱动设计-谈谈 Repository.IUnitO ...
- 谈谈 Repository、IUnitOfWork 和 IDbContext 的实践
谈谈 Repository.IUnitOfWork 和 IDbContext 的实践 上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext ...
- Repository、IUnitOfWork和IDbContext
DDD 领域驱动设计-谈谈Repository.IUnitOfWork和IDbContext的实践 上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDb ...
- Repository、IUnitOfWork 和 IDbContext 的实践
Repository.IUnitOfWork 和 IDbContext 的实践 好久没写 DDD 领域驱动设计相关的文章了,嘎嘎!!! 这几天在开发一个新的项目,虽然不是基于领域驱动设计的,但我想把 ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(1)
好久没写 DDD 领域驱动设计相关的文章了,嘎嘎!!! 这几天在开发一个新的项目,虽然不是基于领域驱动设计的,但我想把 DDD 架构设计的一些东西运用在上面,但发现了很多问题,这些在之前的短消息项目中 ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(转)
http://www.cnblogs.com/xishuai/p/ddd-repository-iunitofwork-and-idbcontext.html 好久没写 DDD 领域驱动设计相关的文章 ...
- Repository、IUnitOfWork 和 IDbContext
1)领域层不应该直接依赖于仓储实现:如果领域层依赖于仓储实现,一是技术绑定太紧密,二是仓储要对领域对象作操作,会造成循环依赖. 2)将接口定义在领域层,减少技术架构依赖,应用层或领域层要使用某个仓储实 ...
随机推荐
- Study note for Continuous Probability Distributions
Basics of Probability Probability density function (pdf). Let X be a continuous random variable. The ...
- POJ1811_Prime Test【Miller Rabin素数测试】【Pollar Rho整数分解】
Prime Test Time Limit: 6000MS Memory Limit: 65536K Total Submissions: 29193 Accepted: 7392 Case Time ...
- 算法战斗:给定一个号码与通配符问号W,问号代表一个随机数字。 给定的整数,得到X,和W它具有相同的长度。 问:多少整数协议W的形式和的比率X大?
如果说: 给定一个号码与通配符问号W,问号代表一个随机数字. 给定的整数,得到X,和W它具有相同的长度. 问:多少整数协议W的形式和的比率X大? 进格公式 数据的多组,两排各数据的,W,第二行是X.它 ...
- Codeforces Round #264 (Div. 2) C Gargari and Bishops 【暴力】
称号: 意甲冠军:给定一个矩阵,每格我们有一个数,然后把两个大象,我希望能够吃的对角线上的所有数字.我问两个最大的大象可以吃值. 分析:这种想法是暴力的主题,计算出每一格放象的话能得到多少钱,然后求出 ...
- HTML介绍JS
首先,该脚本的链接插入HTML代码: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvU2h1aVRpYW5OYWlMdW8=/font/5a6L5L2T/f ...
- Swing中弹出对话框的几种方式(转)
http://www.cnblogs.com/mailingfeng/archive/2011/12/28/2304289.html 在swing中,基于业务的考量,会有对话框来限制用户的行为及对用户 ...
- T-SQL中default值的使用
今天介绍一下通过T-SQL语句来创建表时使用default的关键字来自动使用默认值,这个关键字和其它的如:identity,primary key ,not null ,unique等不是相同,这里简 ...
- [LeetCode145]Binary Tree Postorder Traversal
题目: Given a list, rotate the list to the right by k places, where k is non-negative. For example:Giv ...
- UVA 11769 All Souls Night 的三维凸包要求的表面面积
主题链接:option=com_onlinejudge&Itemid=8&page=show_problem&problem=2869">点击打开链接 求给定的 ...
- 【C语言探索之旅】 第二部分第九课: 实战"悬挂小人"游戏 答案
内容简介 1.课程大纲 2.第二部分第九课: 实战"悬挂小人"游戏 答案 3.第二部分第十课预告: 安全的文本输入 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题, ...