你的位置:首页 > ASP.net教程

[ASP.net教程]设计模式/原则篇


概念

  Unit of Work 即工作单元。 用来维护一组受业务影响的对象列表,将多个操作放在一个单元中,把操作原子化,通过事务统一完成一次提交,如果某个过程出现异常,就将所有修改进行回滚,保证数据的有效状态。同时减少了应用程序与数据库通信,有利于提升系统的性能。

 

具体使用(银行领域的转账建模)

整体项目结构预览

ProjectStructure

构建UnitOfWork.Infrastructure

1、新建Domain文件夹,添加IAggregateRoot接口

IAggregateRoot接口属于聚合根,所有业务对象(Entity)都需要实现聚合根。外部对象需要访问聚合内的实体时,只能通过聚合根进行访问,而不能直接访问。领域模型需要根据领域概念分成多个聚合,每个聚合都有一个实体作为聚合根,通俗的说,领域对象从无到有的创建,以及CRUD的操作都应该作用于聚合根上,而不是单独的某个实体。

2、新建UnitofWork文件夹,添加IUnitofWorkRepository接口

void PersistCreationOf(IAggregateRoot entity);void PersistUpdateOf(IAggregateRoot entity);void PersistDeletionOf(IAggregateRoot entity);

3、添加IUnitofWork接口 注意在添加/修改/删除的方法中传入IUnitofWorkRepository,在提交时Unit Of Work将真正持久化工作放在IUnitofWorkRepository的具体类实现中进行

void RegisterUpdate(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository);void RegisterAdd(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository);void RegisterRemove(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository)void Commit();

 

构建UnitOfWork.Model

1、新建Account类并实现IAggregateRoot接口  a.定义公共的属性Balace(余额) b.定义带参数(balace) 的构造函数

2、添加IAccountRepository接口,将Account持久化  定义添加/更新/删除方法

3、新建AccoutService类 定义公共的IAccountRepository对象和IUnitOfWork对象,通过其构造器实现依赖注入

public IAccountRepository _accountRepository;public IUnitOfWork _unitOfWork;public AccoutService(IAccountRepository accountRepository, IUnitOfWork unitOfWork){  _accountRepository = accountRepository;  _unitOfWork = unitOfWork;}/// <summary>/// 转账/// </summary>/// <param name="from"></param>/// <param name="to"></param>/// <param name="account"></param>public void Transfer(Account from ,Account to ,decimal balace){  if (from.Balace > balace)  {    from.Balace -= balace;    to.Balace += balace;    _accountRepository.Save(from);    _accountRepository.Save(to);    _unitOfWork.Commit();  }}

 

构造UnitOfWork.Repository

1、添加AccountRepository类,AccountRepository实现了IAccountRepository和IUnitofWorkRepository接口,IAccountRepository方法的实现简单地将工作委托至Unit Of Work(传入待持久化实体以及Repository的引用),IUnitofWorkRepository方法中实现了真正的持久化任务,具体的持久化方式可以用ADO.NET/EF/NH等。

public class AccountRepository:IAccountRepository ,IUnitOfWorkRepository{  private IUnitOfWork _unitOfWork;  public AccountRepository(IUnitOfWork unitOfWork)  {    _unitOfWork = unitOfWork;  }  public void Save(Account account)  {    _unitOfWork.RegisterAdd(account,this);  }  public void Update(Account account)  {    _unitOfWork.RegisterUpdate(account,this);  }  public void Remove(Account account)  {    _unitOfWork.RegisterRemove(account,this);  }  public void PersistCreationOf(Infrastructure.Domain.IAggregateRoot entity)  {    //ADO.NET/EF/NH  }  public void PersistUpdateOf(Infrastructure.Domain.IAggregateRoot entity)  {    //ADO.NET/EF/NH  }  public void PersistDeletionOf(Infrastructure.Domain.IAggregateRoot entity)  {     //ADO.NET/EF/NH  }}

2、添加NHUnitOfWork类,实现IUnitofWork接口

public class NHUnitOfWork : IUnitOfWork{  public Dictionary<IAggregateRoot, IUnitOfWorkRepository> addedEntitys;  public Dictionary<IAggregateRoot, IUnitOfWorkRepository> changedEntitys;  public Dictionary<IAggregateRoot, IUnitOfWorkRepository> deletedEntitys;  public NHUnitOfWork()   {    addedEntitys = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();    changedEntitys = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();    deletedEntitys = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>();  }  public void RegisterUpdate(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository)  {    if (!changedEntitys.ContainsKey(entity))    {      changedEntitys.Add(entity,unitOfWorkRepository);    }  }  public void RegisterAdd(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository)  {    if (!addedEntitys.ContainsKey(entity))    {      addedEntitys.Add(entity,unitOfWorkRepository);    }  }  public void RegisterRemove(IAggregateRoot entity, IUnitOfWorkRepository unitOfWorkRepository)  {    if (!deletedEntitys.ContainsKey(entity))    {      deletedEntitys.Add(entity,unitOfWorkRepository);    }  }  public void Commit()  {    using (TransactionScope scope = new TransactionScope())    {      foreach (IAggregateRoot entity in this.addedEntitys.Keys)      {        this.addedEntitys[entity].PersistCreationOf(entity);      }      foreach (IAggregateRoot entity in this.changedEntitys.Keys)      {        this.changedEntitys[entity].PersistUpdateOf(entity);      }      foreach (IAggregateRoot entity in this.deletedEntitys.Keys)      {        this.deletedEntitys[entity].PersistDeletionOf(entity);      }      scope.Complete();    }  }}

NHUnitOfWork类使用3个字典变量来记录对业务实体的代执行修改。addedEntitys 对应于被添加到数据存储的实体,changedEntitys 处理更新的实体,deletedEntitys处理实体删除,与字典中的实体键匹配的IUnitOfWorkRepository将被保存下来,并用于 Commit 方法之中 ,来调用Repository对象,该对象包含真正持久化实体的代码 。Commit方法遍历每一个字典,并调用相应的IUnitOfWorkRepository方法(传递实体引用)。Commit方法中的工作均被 TransactionScope 代码包装起来,如果在IUnitOfWorkRepository中执行任务时出现异常,则所有工作回滚,数据存储将保持原来的状态。

 

构建UnitOfWork.Console

1、添加控制台项目用于验证UnitofWork模式效果

static void Main(string[] args){  Account a = new Account(1000);  System.Console.WriteLine("现在a,存有{0}",a.Balace);  Account b = new Account(200);  System.Console.WriteLine("现在b,存有{0}",b.Balace);  System.Console.WriteLine("a转给b 500元,开始转账......");      IUnitOfWork unitOfWork = new NHUnitOfWork();  IAccountRepository accountRepository = new AccountRepository(unitOfWork);  AccoutService service = new AccoutService(accountRepository,unitOfWork);  service.Transfer(a,b,500);  System.Console.WriteLine("转账结束");  System.Console.WriteLine("a当前金额:{0}",a.Balace);  System.Console.WriteLine("b当前金额:{0}",b.Balace);  System.Console.ReadKey();}

执行后结果如下:

31B2.tmp

UML类图

Unit of Work 

 

总结

对于Unit of Work模式是一种解决方案,一种思路,每个项目的环境都是不一样的,所以我们需要理解的就是其中的原理。以上实例主要是讲解如何部署UnitofWork模式,便于大家的理解,谢谢!

 

疑问讨论

上面讲的是网上资料中最普遍的说法,对经典的Unit of Work模式进行了说明。但是我个人有一个观点需要和大家探讨一下:

先说一个问题,大家在初次接触Unit of Work的时候,会不会有一个疑问——这个和数据库事务有什么区别?

然后我再说我的观点吧。

1.我觉得Unit of Work就是一个思想,工作单元及原子性操作,而这个关联到数据库中,也就是事务了,所以我觉得数据库事务是Unit of Work在数据库中的一种体现。

2.上面讲述的Unit of Work是网上最常见的一种说法。我个人觉得,上面那只是一种实现方式,而在网上看到大部分关于Unit of Work的优点中“减少数据库连接、减少数据库连接时间”,这些都是这种实现方式的优点。但是我认为这并不是Unit of Work真正的意图,我认为Unit of Work只是单纯的原子操作思想,就像我认为微软提供的TransactionScope也是Unit of Work的一种实现一样。

3.我认为上述的这种普遍的说法可能存在这样的问题:

前置条件:数据库表主键为自增的int型ID。

场景: A表一条数据执行了修改操作,B表执行了一个新增操作,接下来有一个if判断,一条分支是拿B表新增数据的ID调用一个webapi,在之后还有一个对C表的修改操作,另一条分支是在D表中新增一条数据。

问题:因为所有的增删改操作都会被延后到工作单元结束的地方进行执行,所以在代码执行到往B表插入数据的时候,实际上没有插入,这时如果在if判断的地方走了第一个分支,用B表插入的数据ID去调用webapi,这将会出现问题。

解决方案:

1.不适用自增ID,比如换成GUID;

2.为webapi实现一个上述的Unit of Work的适配器,将api操作也压入集合中,等到后面一起处理;

3.在工作单元开始的时候就开启一个数据库事务。

第一个解决方案在一些成型系统中再遇到就是不可行的,第二个方案会稍显麻烦,第三个方案就不存在之前说的“减少数据库连接时间”的优点了。

以上是我个人对Unit of Work的观点与疑问,希望大家能够各发己见,一起探讨一下。