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 ...
随机推荐
- SignalR系列续集[系列8:SignalR的性能监测与服务器的负载测试]
目录 SignalR系列目录 前言 也是好久没写博客了,近期确实很忙,嗯..几个项目..头要炸..今天忙里偷闲.继续我们的小系列.. 先谢谢大家的支持.. 我们来聊聊SignalR的性能监测与服务器的 ...
- SDWebImage源码解读之SDWebImageCache(下)
第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...
- JAVA FreeMarker工具类
FreeMarkerUtil.java package pers.kangxu.datautils.utils; import java.io.File; import java.io.StringW ...
- Flexible 弹性盒子模型之CSS flex-shrink 属性
实例 让第二个元素收缩到其他元素的三分之一: 效果预览 div:nth-of-type(2){flex-shrink:3;} 浏览器支持 表格中的数字表示支持该属性的第一个浏览器的版本号. 紧跟在 - ...
- Dynamics CRM 之ADFS 使用 WID 的独立联合服务器
ADFS 的使用 WID 的独立联合服务器适用于自己的测试环境,常用的就是在虚机中使用. 拓扑图如下: wID:联合身份验证服务配置为使用 Windows 内部数据库
- Oracle 分页
--1:无ORDER BY排序的写法.(效率最高) --(经过测试,此方法成本最低,只嵌套一层,速度最快!即使查询的数据量再大,也几乎不受影响,速度依然!) SELECT * FROM (SELECT ...
- Oracle补全日志(Supplemental logging)
Oracle补全日志(Supplemental logging)特性因其作用的不同可分为以下几种:最小(Minimal),支持所有字段(all),支持主键(primary key),支持唯一键(uni ...
- Linux虚拟化学习笔记<一>
关于虚拟化,原理的东西是非常复杂的,要想完全理解,没有足够的耐心是不不能完全学透这部分内容的.那下面我主要以资源汇总的形式把一些资料罗列出来,帮助大家快速理解虚拟化,快速使用和配置. 为什么要虚拟化: ...
- 1 selenium3.0.1无法打开火狐浏览器
[问题描述] 1.配置selenium3.0和java后,尝试打开火狐浏览器,提示缺少geckodriver驱动. [解决方案] 1.在http://www.seleniumhq.org/downlo ...
- 我的MYSQL学习心得(十五) 日志
我的MYSQL学习心得(十五) 日志 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据 ...