0%

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

{

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;

}

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)

{

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

var container = new UnityContainer();

container.RegisterType<IDbContext, ExamDbConext>(``new ContainerControlledLifetimeManager());

container.RegisterType<IUnitOfWork, EfUnitOfWork>();

var unitofWork = container.Resolve<IUnitOfWork>();

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);

unitofWork.Commit();

orderRepository.Delete(order);

unitofWork.Commit();