ABP框架 - 工作单元
本节内容:
简介
在一个使用数据库的应用里,连接和事务是非常重要的,何时打开一个连接,何时开启一个事务,如果释放连接等等。ABP通过工作单元系统管理数据库的连接和事务。
在ABP中管理连接和事务
当进行一个工作单元方法,ABP打开一个数据库连接(可能不是马上打开,在第一次用到数据库时打开,这要看ORM供应器的实现了)和开始一个事务,所以你可以安全地在这个方法内使用连接,在这个方法的最后,提交事务并释放连接,如果这个方法抛出任何的异常,回滚事务并释放连接,这种方式,一个工作单元就是原子性的。ABP自动完成这些操作。
如果一个工作单元方法调用另一个工作单元方法,两个会使用同一个连接和事务,第一个方法管理连接和事务,另一个使用它们。
约定的工作单元方法
有些方法默认为工作单元方法:
- 所有Mvc、Web Api和Asp.net Core Mvc控制器的Actions。
- 所有应用服务方法。
- 所有仓储方法。
假设我们有一个应用服务方法,如下所示:
public class PersonAppService : IPersonAppService
{
private readonly IPersonRepository _personRepository;
private readonly IStatisticsRepository _statisticsRepository; public PersonAppService(IPersonRepository personRepository, IStatisticsRepository statisticsRepository)
{
_personRepository = personRepository;
_statisticsRepository = statisticsRepository;
} public void CreatePerson(CreatePersonInput input)
{
var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
_personRepository.Insert(person);
_statisticsRepository.IncrementPeopleCount();
}
}
在CreatePerson方法里,我们使用人员仓储插入一个人员,并使用统计仓储递增人员总数,这两个仓储共享相同的连接和事务,因为应用服务方法默认为一个工作单元。当进入CreatePerson方法时,ABP打开一个连接并开始一个事务,在方法结束时,如果没有异常出现,就提交事务并释放连接,这种方式,所有数据库操作,在CreatePerson方法里都变成是原子性的。
控制工作单元
上面的方法里,工作单元是暗中工作的。web应用里大部分情况,我们都不用控制工作单元。如果你想在某些地方控制工作单元,你可以显式地使用它,有两种方法可以实现。
UnitOfWork 特性
优先推荐的方法是:使用UnitOfWork特性,例如:
[UnitOfWork]
public void CreatePerson(CreatePersonInput input)
{
var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
_personRepository.Insert(person);
_statisticsRepository.IncrementPeopleCount();
}
因此,CreatePerson方法成为一个工作单元,管理数据库连接和事务,两个仓储使用相同的工作单元,注意,如果在一个应用服务方法里,不需要使用UnitOfWork特性。 请参见”UnitOfWork 特性限制“小节。
UnitOfWork特性有些选项,参考下方的“工作单元详情”。
IUnitOfWorkManager
第二种方法是:使用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())
{
_personRepository.Insert(person);
_statisticsRepository.IncrementPeopleCount(); unitOfWork.Complete();
}
}
}
如上所示,你可以映射并使用IUnitOfWorkManager(有些基类默认已经被注入UnitOfWorkManager:Mvc控制器、应用服务、领域服务等),因此,你可以创建更多有域限制的工作单元。用这种方法,你应该手动调用Complete方法,如果你不调用,事务会回滚,修改不会被保存。
Begin方法有几个设置工作单元选项的重载,如果没有好的理由,最好使用UnitOfWork特性。
工作单元详情
禁用工作单元
你如果想为约定的工作单元方法禁用工作单元,使用UnitOfWork特性的IsDisabled属性。例如:
[UnitOfWork(IsDisabled = true)]
public virtual void RemoveFriendship(RemoveFriendshipInput input)
{
_friendshipRepository.Delete(input.Id);
}
通常,你不会想这么做,但在有些情况下你需要禁用工作单元:
- 你可能想限制工作单元的域,如同上面所述,就使用UnitOfWorkScope。
注意:如果一个工作单元方法调用这个RemoveFriendship方法,禁用特性会被忽略,该方法使用与调用者方法相同的工作单元。所以小心使用禁用特性。同样地,上面的代码也会工作得很好,因为仓储方法默认是个工作单元。
非事务性工作单元
一个工作单元默认就是事务性的,因此ABP开始/提交/回滚数据库级别事务。在一些特殊情况下,事务可能会引起问题,因为它可能锁住数据库的一些行或表。这种情况下,你可能想禁用数据库级别事务。UnitOfWork特性可以在它的构造器里,获得一个boolean值作为非事务性标识。使用示例:
[UnitOfWork(isTransactional: false)]
public GetTasksOutput GetTasks(GetTasksInput input)
{
var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);
return new GetTasksOutput
{
Tasks = Mapper.Map<List<TaskDto>>(tasks)
};
}
我建议[UnitOfWork(isTransactional:false)]这样使用这个特性,我认为这样更可读更明确。但是你也可以[UnitOfWork(false)]这样用。
注意:ORM框架(如NHibernate和EntityFramework)内部使用一个单独的命令保存修改。假设你在一个非事务工作单元里更新一些实体,即使是这种况下,所有更新也是在工作单元的最后,执行一个单独的数据库命令。但是,如果在一个非事务性工作单元里,直接执行一条Sql语句,它会被直接执行并且不会被回滚。
非事务性工作单元有一个限制,如果你已经在一个事务性工作单元域内,把isTransactional设置false也会被忽略(在一个事务性工作单元里,使用事务域选项创建一个非事务性工作单元)。
使用非事务性工作单元要小心,因为大部分情况下,应当用事务性保证数据完整性。如果你只是读取数据,而不做修改,那么就可以安全地使用非事务性。
一个工作单元方法调用另一个方法
工作单元是环绕的,如果一个工作单元方法调用另一个工作单元方法,他们共享相同的连接和事务,第一个方法管理连接和事务,另一个方法使用它们。
工作单元域
你可以在一个事务里创建另一个独立的事务,或在一个事务里创建一个非事务性的域,.NET为它定义了TransactionScopeOption。你可以通过设置工作单元域选项来控制它。
自动保存修改
如果一个方法是工作单元,ABP会自动在这个方法的最后,保存所有修改。假设我们需要一个方法更新person的name:
[UnitOfWork]
public void UpdateName(UpdateNameInput input)
{
var person = _personRepository.Get(input.PersonId);
person.Name = input.NewName;
}
这就是所有代码,name会被修改!我们甚至不用调用_personRepository.Update方法。在一个工作单元里,ORM框架跟踪实体的所有修改,并反射到数据库。
注意:不需要为约定的工作单元方法声明UnitOfWork。
IRepository.GetAll() 方法
当你在一个仓储方法外调用GetAll(),它必须要有一个打开的数据库连接,因为它只是返回IQueryable。这是必须的,因为IQuery的延迟执行。它没有执行数据库查询,除非你调用ToList()或在一个foreach循环里使用IQueryable(或以某种方式访问查询里的项),所以当你调用ToList()方法时,数据库连接必须可用。
考虑如下示例:
[UnitOfWork]
public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
{
//Get IQueryable<Person>
var query = _personRepository.GetAll(); //Add some filters if selected
if (!string.IsNullOrEmpty(input.SearchedName))
{
query = query.Where(person => person.Name.StartsWith(input.SearchedName));
} if (input.IsActive.HasValue)
{
query = query.Where(person => person.IsActive == input.IsActive.Value);
} //Get paged result list
var people = query.Skip(input.SkipCount).Take(input.MaxResultCount).ToList(); return new SearchPeopleOutput { People = Mapper.Map<List<PersonDto>>(people) };
}
SearchPeople方法必须是工作单元,因为IQueryable的ToList()方法在这个方法里被调用。并且当IQueryable.ToList()被执行时,数据库连接必须打开着。
大多数情况,在一个web应用里,使用GetAll方法是安全的,因为所以控制的Action默认都是工作单元并且数据库连接在整个请求里都是可用的。
UnitOfWrok 特性限制
可以使用UnitOfWork为:
- 类的所有public或public virtual方法并且在接口之上应用了这个特性(如一个应用服务就是在服务接口上使用了这个特征)。
- 类的所有public virtual并自注入的方法(如Mvc控制器和Web Api控制器)。
- 所有protected virtual方法。
建议一直使用virtual方法,你可以不为private方法这么做,因为ABP为它们使用动态代理,而private方法对于继承它的类来说是看不见的。如果不使用依赖注入并自己实例化这个类,UnitOfWork特性(和任何代理)都无法工作。
选项
有些选项可以改变一个工作单元的行为。
首先,我们可以在启动配置里修改所有工作单元的默认值,这通常在我们模块的预初始化方法里。
public class SimpleTaskSystemCoreModule : AbpModule
{
public override void PreInitialize()
{
Configuration.UnitOfWork.IsolationLevel = IsolationLevel.ReadCommitted;
Configuration.UnitOfWork.Timeout = TimeSpan.FromMinutes();
} //...other module methods
}
其次,我们可以为一个特殊工作单元重写默认值,为此,UnitOfWork特性构造器和IUnitOfWorkManager.Begin方法有获取选项的重载。
最后,你可以通过启动配置,为Asp.net Mvc、Web Api和Asp.net Core Mvc控制器配置工作单元特性默认值(查看它们各自的文档)。
方法
UnitOfWork系统悄无声息、无缝地工作着,但是在有些特殊情况下,你需要调用它们的方法。
你可以用如下作一种方式,访问当前工作单元:
- 如果你的类是从有些特殊基类(ApplicationService、AbpController、AbpApiController等)继承而来,可以直接使用CurrentUnitOfWork属性。
- 你可以把IunitOfWorkManager注入到任何类里,然后使用IUnitOfWorkManager.Current属性。
SaveChanges
ABP在一个工作单元的最后,保存所有修改,你不必做任何事,但是有时你想在一个工作单元操作的中间保存修改到数据库,例如在EntityFramework里,为获取新实体的Id,先进行保存。
你可以使用当前工作单元的SaveChanges或SaveChangesAsync方法。
注意:如果当前工作单元是事务性的,所有的修改在出现异常情况下回滚,不然保存。
事件
一个工作单元有Completed、Failed和Disposed事件,你可以注册这些事件然后执行需要的操作。例如,你想在当前工作单元成功完成后运行一些代码:
public void CreateTask(CreateTaskInput input)
{
var task = new Task { Description = input.Description }; if (input.AssignedPersonId.HasValue)
{
task.AssignedPersonId = input.AssignedPersonId.Value;
_unitOfWorkManager.Current.Completed += (sender, args) => { /* TODO: Send email to assigned person */ };
} _taskRepository.Insert(task);
}
kid1412附:英文原文:http://www.aspnetboilerplate.com/Pages/Documents/Unit-Of-Work
ABP框架 - 工作单元的更多相关文章
- 手工搭建基于ABP的框架 - 工作单元以及事务管理
一个业务功能往往不只由一次数据库请求(或者服务调用)实现.为了功能的完整性,我们希望如果该功能执行一半时出错,则撤销前面已执行的改动.在数据库层面上,事务管理实现了这种完整性需求.在ABP中,一个完整 ...
- ABP的工作单元
http://www.aspnetboilerplate.com/Pages/Documents/Unit-Of-Work 工作单元位于领域层. ABP的数据库连接和事务处理: 1,仓储类 ASP ...
- Abp之工作单元与事务
环境:Abp1.2 疑问:没有调用工作单元的SaveChanges方法引起的事务提交时机的问题. 例如:有一个应用服务代码如下: public void CreatePhrase(PhraseCrea ...
- 【ABP】工作单元——不进行事物独立执行功能
1.注入 private readonly IUnitOfWorkManager unitOfWorkManager; 2.构造 3.开启新事物 using (var unitOfWork = uni ...
- ABP官方文档翻译 3.6 工作单元
工作单元 介绍 ABP中的连接和事务管理 传统的工作单元方法 控制工作单元 UnitOfWork特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 无事务工作单元 一个工作单元方法 ...
- EntityFrameworkCore之工作单元的封装
1. 简介 2. DbContext 生命周期和使用规范 2.1. 生命周期 2.2. 使用规范 2.3. 避免 DbContext 线程处理问题 3. 封装-工作单元 3.1. 分析 3.2. 设计 ...
- 解析ABP框架中的事务处理和工作单元,ABP事务处理
通用连接和事务管理方法连接和事务管理是使用数据库的应用程序最重要的概念之一.当你开启一个数据库连接,什么时候开始事务,如何释放连接...诸如此类的. 正如大家都知道的,.Net使用连接池(connec ...
- [ABP]浅谈工作单元 在整个 ABP 框架当中的应用
ABP在其内部实现了工作单元模式,统一地进行事务与连接管理. 其核心就是通过 Castle 的 Dynamic Proxy 进行动态代理,在组件注册的时候进行拦截器注入,拦截到实现了 Unit Of ...
- 浅谈工作单元 在整个 ABP 框架当中的应用
ABP在其内部实现了工作单元模式,统一地进行事务与连接管理. 其核心就是通过 Castle 的 Dynamic Proxy 进行动态代理,在组件注册的时候进行拦截器注入,拦截到实现了 Unit Of ...
随机推荐
- 自定义基于 VLC 的视频播放器
前言(蛋疼的背景故事) 前段时间,接了一个小项目,有个需求是要在系统待机一段时间以后,循环播放 MV(类似于 Windows 系统的屏幕保护). 听到这个需求,我首先想到的是 MediaPlayer ...
- 【转】39个让你受益的HTML5教程
闲话少说,本文作者为大家收集了网上学习HTML5的资源,期望它们可以帮助大家更好地学习HTML5. 好人啊! 不过,作者原来说的40个只有39个,因为第5个和第8个是重复的. 原文在此! 1. 五分钟 ...
- c# 基础 object ,new操作符,类型转换
参考页面: http://www.yuanjiaocheng.net/webapi/config-webapi.html http://www.yuanjiaocheng.net/webapi/web ...
- Linux杀死进程,查看进程
http://blog.csdn.net/wojiaopanpan/article/details/7286430/
- 【搬砖】安卓入门(1)- Java开发入门
01.01_计算机基础知识(计算机概述)(了解) A:什么是计算机?计算机在生活中的应用举例 计算机(Computer)全称:电子计算机,俗称电脑.是一种能够按照程序运行,自动.高速处理海量数据的现代 ...
- Android之使用Bundle进行IPC
一.Bundle进行IPC介绍 四大组件中的三大组件(Activity.Service.Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口 ...
- Android开发学习——动画
帧动画> 一张张图片不断的切换,形成动画效果* 在drawable目录下定义xml文件,子节点为animation-list,在这里定义要显示的图片和每张图片的显示时长 ...
- Linux下编译安装Vim8.0
什么是Vim? Vim 是经典的 UNIX 编辑器 Vi 的深度改良版本.它增加了许多功能,包括:多级撤销.格式高亮.命令行历史.在线帮助.拼写检查.文件名补完.块操作.脚本支持,等等.除了字符界面版 ...
- echo命令
linux的echo命令, 在shell编程中极为常用, 在终端下打印变量value的时候也是常常用到的, 因此有必要了解下echo的用法echo命令的功能是在显示器上显示一段文字,一般起到一个提示的 ...
- 自制Azure中国版“加血包”
Micrsoft Azure中国版的国际出口最近升级为电话线拨号模式,目测为10个用户共享一条56kb的电话线拨号链路.有图有真相: 中国的IT从业者,有三分之一的职业生涯时间是在跟网络斗智斗勇.这点 ...