前言:
什么是工作单元:
意思是可以“工作”的单元:我们再看一个现实中例子,也最能说明Unit Of Work所包含的意思,就是银行转账操作,包含两个动作:转出方扣钱和转入方加钱,这两个动作要么都完成,要么都不完成,也就是事务操作,完成就Commit(提交),完不成就Rollback(回滚)。
回到Unit Of Work的定义,Unit of Work是用来解决领域模型存储和变更工作,在ORM进行持久化的时候,比如Entity Framework的SaveChanges操作,其实就可以看做是Unit Of Work,也就是定义中所说“用来解决领域模型存储和变更工作”,但是如果项目是基于Entity Framework进行DDD(领域驱动设计)开发设计的,那Entity Framework中的Domain Model就必然包含业务逻辑,这就不符合“而这些数据层业务并不属于领域模型本身具有的”,也就是说Unit Of Work必须独立于Domain Layer(领域层),注意独立的业务是“数据层”业务,并不是业务场景中的“业务”,比如“转账业务”,转出方扣钱和转入方加钱这个业务就属于“数据层业务”,有的人会把Unit Of Work放在Domain Layer(领域层)中,其实是有些不恰当的,应该是放在Infrastructure Layer(基础层)中,但其实也只是相对而言,如果涉及到具体的业务单元模块,具体实现可以放在领域层中
简单来说:
在很多操作中我们可能是进行多项操作的,比如 同时保存两个表的数据,如果我们先保存一个表(SaveChanges()之后),再保存另一个表(SaveChanges())其实是两个不同的事务,通俗的讲,我们对数据库提交了两次数据。而UnitOfWork 就是 把本次操作(分别保存两张表)放在统一事务中,所有的CRUD完成后,统一提交数据,即保证数据的一致性,又减少了提交次数,提高性能。
仓储类
在DDD(领域驱动设计)开发设计中,Unit Of Work的使用一般会结合Repository(仓储)使用,有关Repository可以参阅dudu的一篇文章:http://www.cnblogs.com/dudu/archive/2011/05/25/repository_pattern.html,文中的解释很清楚直白:
Repository:是一个独立的层,介于领域层与数据映射层(数据访问层)之间。它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问。Repository是仓库管理员,领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要知道东西实际放在哪。
Unit Of Work所做的工作可以看做是收集Repository出入库的“商品”,便于一次装车,运输过程中如果没有出现问题,那这车的所有“商品”就安全到达,如果出现问题,那这车的所有“商品”全部打回,这辆车就是“单元”的意思。
ABP中的连接和事务管理
Unit Of Work模式,由马丁大叔提出,是一种数据访问模式。UOW模式的作用是在业务用例的操作中跟踪对象的所有更改(增加、删除和更新),并将所有更改的对象保存在其维护的列表中。在业务用例的终点,通过事务,一次性提交所有更改,以确保数据的完整性和有效性。总而言之,UOW协调这些对象的持久化及并发问题。
如果手动实现的思路:
https://blog.csdn.net/sigxxl/article/details/7875260
http://www.cnblogs.com/yuangang/p/5725201.html
第一话:强大的ABP
在ABP框架中默认把我们的应用服务都注册了工作单元,假设我们有一个像下面的应用服务:
在Create方法中,我们使用了person仓储插入了一个person,而且使用user仓储增加总人数。在这里例子中,这两个仓储共享相同的连接和事务,因为它们在一个应用服务方法中。ABP在进入CreatePerson方法时打开一个数据库连接并开始一个事务,如果没有抛出异常事务会在方法结尾时提交,如果有任何异常发生,将会回滚。这样一来,在CreatePerson方法中的所有数据库操作都成了原子的(工作单元)。
public virtual async Task<PersonEditDto> Create(PersonEditDto input)
{//插入person数据
var entity = input.MapTo<Person>();
entity =await _entityRepository.InsertAsync(entity);
//为person插入对应的tel表
var telentity = new Tel
{ Id = entity.Id,
PersonId = entity.Id,
PhoneNumber = "1"
};
var tel = await _TelRepository.InsertAsync( telentity );
return entity.MapTo<PersonEditDto>();
}
第二步:如果非服务应用,只是一个类,我们怎么用呢?
声明[UnitOfWork]特性
[UnitOfWork]
public virtual async Task<PersonEditDto> Create(PersonEditDto input)
{
var entity = input.MapTo<Person>();
entity =await _entityRepository.InsertAsync(entity);
var telentity = new Tel
{ //Id = entity.Id,
PersonId = entity.Id,
PhoneNumber = "1"
};
var tel = await _TelRepository.InsertAsync( telentity );
return entity.MapTo<PersonEditDto>();
}
第二种方法是使用IUnitOfWorkManager.Begin()方法,如下所示:
public class MyService
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IPersonRepository _personRepository;
private readonly IStatisticsRepository _statisticsRepository;
public MyService(IUnitOfWorkManager unitOfWorkManager, IPersonRepository personRepository, IStatisticsRepository statisticsRepository)
{
_unitOfWorkManager = unitOfWorkManager;
_personRepository = personRepository;
_statisticsRepository = statisticsRepository;
}
public void CreatePerson(CreatePersonInput input)
{
var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
using (var unitOfWork = _unitOfWorkManager.Begin())
{
//插入person数据
var entity = input.MapTo<Person>();
entity =await _entityRepository.InsertAsync(entity);
//为person插入对应的tel表
var telentity = new Tel
{ Id = entity.Id,
PersonId = entity.Id,
PhoneNumber = "1"
};
var tel = await _TelRepository.InsertAsync( telentity );
unitOfWork.Complete();
}
}
}
第三步:关闭工作单元
是的ABP框架的服务应用是默认帮我们实现了工作单元,我们一直在使用工作单元但是我们却不了解他
本篇我们就深入了解下他。如果先发现我们失去了工作单元会发送什么那就把工作单元关闭。
下面可以看到他会报错
[UnitOfWork(IsDisabled = true)]
public virtual async Task<PersonEditDto> Create(PersonEditDto input)
{
var entity = input.MapTo<Person>();
entity =await _entityRepository.InsertAsync(entity);
var telentity = new Tel
{ //Id = entity.Id,
PersonId = entity.Id,
PhoneNumber = "1"
};
var tel = await _TelRepository.InsertAsync( telentity );
return entity.MapTo<PersonEditDto>();
}
第四步:关闭工作单元的事务
工作单元默认是事务的(本质如此)。因此,ABP会开始->提交->回滚一个显式的数据库级别的事务。在一些特殊场合,事务可能会造成问题,因为它可能会锁住数据库中的一些行或者表。在这种情况下,你可能想关闭数据库级别的事务。UnitOfWork特性可以在构造函数中获得一个布尔值,从而以非事务形式工作。用法如下:
[UnitOfWork(isTransactional: false)] :关闭事务后,如果User创建报错就不会回滚
[UnitOfWork(isTransactional: false)]
public virtual async Task<PersonEditDto> Create(PersonEditDto input)
{
var entity = input.MapTo<Person>();
entity =await _entityRepository.InsertAsync(entity);
var telentity = new Tel
{ //Id = entity.Id,
PersonId = entity.Id,
PhoneNumber = "1"
};
var tel = await _TelRepository.InsertAsync( telentity );
return entity.MapTo<PersonEditDto>();
}
假设你在一个非事务工作单元里更新一些实体,即使是这种况下,所有更新也是在工作单元的最后,执行一个单独的数据库命令。但是,如果在一个非事务性工作单元里,直接执行一条Sql语句,它会被直接执行并且不会被回滚。
使用非事务性工作单元要小心,因为大部分情况下,应当用事务性保证数据完整性。如果你只是读取数据,而不做修改,那么就可以安全地使用非事务性。
SaveChanges
(在非事务性工作的时候,可以不受下面错误语句的影响。或者在想先让上面执行,下面要上面的数据的时候)
在工作单元结束时ABP会保存所有的更改,而你不需要做任何事情。但是有些时候,你或许会想要在工作单元的执行过程中就保存更改。例如:使用EntityFramerok保存一个新实体的时候取得一个这个实体的ID。 保存并调出
你可以使用当前工作单元的 SaveChanges 或者 SaveChangesAsync 方法。
如下面代码使用了 SaveChanges后就可以,先获取entity.Id了
public virtual async Task<PersonEditDto> Create()
{
PersonEditDto input = new PersonEditDto {Name="12",Sex=0 };
var entity = input.MapTo<Person>();
entity = await _entityRepository.InsertAsync(entity);
//没这个是不可以先获取Id的
CurrentUnitOfWork.SaveChanges();
var telentity = new Tel
{
Id = entity.Id,
PersonId = entity.Id,
PhoneNumber = "1"
};
var tel = await _TelRepository.InsertAsync(telentity);
return entity.MapTo<PersonEditDto>();
}
注意:如果当前工作单元是事务性的,那么这个方法将会无效。
第五步:如果我们的GetAll没了工作单元
当在一个仓储方法之外调用GetAll()时,必须存在一个打开的数据库连接,因为GetAll返回了IQueryable,而且IQueryable会延迟执行。直到调用ToList()方法或者在foreach循环中使用IQueryable,才会真正执行数据库查询。因此,调用ToList()方法时,数据库连接必须是活着的(alive)。 关闭后查询就不可以用
考虑一下下面的例子:
[UnitOfWork(IsDisabled =true)]
public async Task<List<Person>> GetAll()
{
var entity = _entityRepository.GetAll();
return entity.ToList();
}
第六步:自动保存
就下面2行代码person的sex就改变了。我们甚至不用调用_personRepository.Update方法。ORM框架会跟踪工作单元中实体的所有改变,并将改变反应给数据库。
注意没有必要为应用服务方法声明UnitOfWork特性,因为它们默认已经是工作单元了。
public string Update(int id,int sex)
{
var person = _entityRepository.Get(id);
person.Sex = sex;
return "完成";
}
番外改变一下工作单元的参数
public class SimpleTaskSystemCoreModule : AbpModule
{
public override void PreInitialize()
{
Configuration.UnitOfWork.IsolationLevel = IsolationLevel.ReadCommitted;
Configuration.UnitOfWork.Timeout = TimeSpan.FromMinutes(30);
}
//...其他模块方法
}
转载:
https://www.cnblogs.com/farb/p/ABPUnitofWork.html#ways
https://www.cnblogs.com/xishuai/p/3750154.html
https://www.jianshu.com/p/6f22d7bcb87a