ABP官方文档翻译 3.6 工作单元
工作单元
介绍
在使用数据库的应用中,连接和事务管理是最重要的概念之一。什么时候打开连接,什么时候开始一个事务,如何释放连接等等。ABP使用工作单元系统来管理连接和事务。
ABP中的连接和事务管理
当进入到一个工作单元方法时,ABP打开一个数据库连接(可能不会立即打开,但在首次使用时打开的,基于ORM提供者如何实现)并开始一个事务。所以,你可以在这个方法中安全的使用连接,在方法的结尾处,事务会提交,连接被释放。如果方法抛出了任何异常,事务会回滚,连接被释放。使用这种方式,工作单元方法是原子的(一个工作单元)。ABP自动完成这些操作。
如果一个工作单元方法调用另一个工作单元方法,两者使用相同的连接和事务。首次进入的方法管理连接和事务,其他的方法使用它。
传统的工作单元方法
一些方法默认是工作单元方法:
- 所有的MVC、Web API和ASP.NET Core MVC控制器操作。
- 所有的应用服务方法。
- 所有的仓储方法。
假定我么有一个应用服务方法,如下:
- 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方法中,我们使用person仓储插入了一个person,并使用静态仓储增加people的总数。在这个例子中,两个仓储共享相同的连接和事务,因为应用服务方法默认是一个工作单元。当进入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特性有些选项。参见“工作单元详情”部分
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特性,它更好且简短。
工作单元详情
禁用工作单元
你可能希望禁用传统工作单元方法的工作单元,可以使用UnitOfWorkAttribute特性的IsDisabled属性。示例用法:
- [UnitOfWork(IsDisabled = true)]
- public virtual void RemoveFriendship(RemoveFriendshipInput input)
- {
- _friendshipRepository.Delete(input.Id);
- }
正常来讲,你不希望这样做,但是在某些场景下,你或许希望禁用工作单元。
- 你或许希望使用UnitOfWorkScope类将工作单元在一个限制范围内使用,如上描述的那样。
注意,如果一个工作单元方法调用这个RemoveFriendShip方法,禁用这个方法会被忽略,它和调用方法使用相同的工作单元。所以,使用禁止需要小心。因为仓储方法默认为工作单元的,所以上面这些代码可以良好的工作。
无事务工作单元
工作单元默认是事务的(本性如此)。因此,ABP开始/提交/回滚一个显示的数据库级别的事务。在某些特殊情况下,事务会导致问题,因为它可能会锁定数据库中的行或表。在这种情况下,你或许会希望禁用数据库级别的事务。UnitOfWork特性可以在它的构造函数中获取一个布尔值来以无事务的方式工作。
示例用法:
- [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)内部在一个命令里保存更改。假定,你在一个无事务UOW里更新了一些实体。即使在这种情况下,所有的更新操作都在工作单元的结尾使用一个数据库命令来执行。但是,如果你直接执行一个SQL查询,他会立即执行并且不会回滚,如果UOW是无事务的话。
无事务UOWs有一个限制。如果你在一个事务工作单元范围内,设置isTransactional为false会被忽略(使用事务范围选项在一个事务工作单元中创建一个无事务工作单元)。
需要小心使用无事务工作单元,因为大多数时候为了确保数据完成性应使用事务。如果你的方法仅仅读取数据而不改变他,那么它可以是无事务且安全的。
一个工作单元方法调用另一个
工作单元是环绕的。如果一个工作单元的方法调用另一个工作单元的方法,他们共享同样的连接和事务。第一个方法管理连接,其他的使用它。
工作单元范围
你可以在另一个事务里创建一个不同和隔离的事务或者在一个事务里创建一个非事务范围。.NET定义了TransactionScopeOption来实现这个功能,你可以设计工作单元的范围选项来控制它。
自动保存更改
如果一个方法时工作单元的,ABP自动在方法结束时保存所有的更改。假定,我们需要更新person名字的方法:
- [UnitOfWork]
- public void UpdateName(UpdateNameInput input)
- {
- var person = _personRepository.Get(input.PersonId);
- person.Name = input.NewName;
- }
就这样,名字被更改了!我们甚至不用调用_personRepository.Update方法。ORM框架在一个工作单元里保持跟踪实体的所有跟踪,并且将更改反映到数据库中。
注意,对于传统的工作单元方法不需要声明UnitOfWork。
IRepository.GetAll()方法
当你在仓储方法之外调用GetAll()时,必须有一个打开的数据库连接,因为它返回的时IQueryable。z这是必须的,因为IQueryable是延迟执行的。除非你调用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方法是安全的,因为所有的控制器动作默认都是工作单元的,因此在整个请求中数据库连接都是可用的。
工作单元特性限制
你可以这样使用UnitOfWork特性:
- 基于接口使用的类(如应用服务基于服务接口使用)的所有的public或public virtual方法。
- 自注入类(如MVC Controllers和WebAPI Controllers)的所有public virtual方法。
- 所有的protected virtual方法。
建议使用虚方法,但是不能做为私有方法使用。因为,ABP为虚方法使用动态代理,私有方法对继承类是不可见的。如果你不使用动态注入并实例化类, UnitOfWork特性(和任何代理)将不能工作。
选项
这有一些选项可以用来更改工作单元的行为。
首先,我们可以在启动配置里,更改所有工作单元的默认值。通常在我们模块的PreInitialize方法中实现。
- 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控制器(参见他们的文档)默认的工作单元特性。
方法
工作单元系统无缝的工作且不可见的。但是,在一些特殊情况下,你需要调用它的方法。
你可以使用两种方式中的一种访问当前的工作单元:
- 你可以直接使用CurrentUnitOfWork属性,如果你的类是继承自一些特殊的基类(应用服务、领域服务、AbpController、AbpApiController...等等)。
- 你可以在任何类中注入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);
- }
ABP官方文档翻译 3.6 工作单元的更多相关文章
- ABP官方文档翻译 9.1 EntityFramework集成
EntityFramework集成 Nuget包 DbContext 仓储 默认仓储 自定义仓储 应用特定的基础仓储类 自定义仓储示例 仓储最佳实践 事务管理 数据存储 ABP可以使用ORM框架,它内 ...
- ABP官方文档翻译 6.2.1 ASP.NET Core集成
ASP.NET Core 介绍 迁移到ASP.NET Core? 启动模板 配置 启动类 模块配置 控制器 应用服务作为控制器 过滤器 授权过滤器 审计Action过滤器 校验过滤器 工作单元Acti ...
- ABP官方文档翻译 6.1.1 MVC控制器
ASP.NET MVC控制器 介绍 AbpController基类 本地化 其他 过滤器 异常处理和结果包装 审计日志 验证 授权 工作单元 介绍 ABP通过Abp.Web.Mvc nuget包集成到 ...
- ABP官方文档翻译 5.1 Web API控制器
ASP.NET Web API控制器 介绍 AbpApiController基类 本地化 其他 过滤器 审计日志 授权 反伪造过滤器 工作单元 结果包装和异常处理 结果缓存 校验 模型绑定器 介绍 A ...
- ABP官方文档翻译 4.1 应用服务
应用服务 IApplicationService接口 ApplicationService类 CrudService和AsyncCrudAppService类 简单的CRUD应用服务示例 自定义CRU ...
- ABP官方文档翻译 3.8 数据过滤器
数据过滤器 介绍 预定义过滤器 ISoftDelete 何时使用? IMustHaveTenant 何时使用? IMayHaveTenant 何时使用 禁用过滤器 关于using语句 关于多租户 全局 ...
- ABP官方文档翻译 3.7 领域事件(事件总线)
领域事件(事件总线) 事件总线 注入IEventBus 获取默认实例 定义事件 预定义事件 处理异常 实体更改 触发事件 处理事件 处理基础事件 处理者异常 处理多个事件 注册处理者 自动 手动 取消 ...
- ABP官方文档翻译 3.3 仓储
仓储 默认仓储 自定义仓储 自定义仓储接口 自定义仓储实现 基础仓储方法管理数据库连接 查询 获取单个实体 获取实体列表 关于IQueryable 自定义返回值 插入 更新 删除 其他 关于异步方法 ...
- ABP官方文档翻译 3.1 实体
实体 实体类 聚合根类 领域事件 常规接口 审计 软删除 激活/失活实体 实体改变事件 IEntity接口 实体是DDD(领域驱动设计)的核心概念之一.Eric Evans描述它为"An o ...
随机推荐
- 51 nod 1211 数独 DLX
原题链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1211 调了挺久的,自己的一份舞蹈链模板…… 算是在网上见到的模 ...
- 最长上升子序列(LIS) dp学习~3
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1087 Super Jumping! Jumping! Jumping! Time Limit: 200 ...
- Educational Codeforces Round 2_B. Queries about less or equal elements
B. Queries about less or equal elements time limit per test 2 seconds memory limit per test 256 mega ...
- .26-浅析webpack源码之事件流make(1)
compilation事件流中,依然只是针对细节步骤做事件流注入,代码流程如图: // apply => this-compilation // apply => compilation ...
- 分布式文件系统FastDFS动态扩容
当用户量越来越大,则集群中某个group总会到达其极限,这时就得扩展集群的容量了. FastDFS的扩容分为对group纵向扩容和横向扩容 纵向扩容 指在同一个group组中增加服务器,实现数据冗余, ...
- nginx重启几种方法
http://blog.csdn.net/zqinghai/article/details/71125045 ps -ef|grep nginx 平滑重启命令: kill -HUP 住进称号或进程号文 ...
- 月薪20k以上的高级程序员需要学习哪些技术呢?
课程内容: 源码分析.分布式架构.微服务架构.性能优化.团队协作效率.双十一项目实战 适用对象: 1-5年或更长软件开发经验,没有工作经验但基础非常扎实,对java工作机制,常用设计思想,常用java ...
- ios 积累
1.加号 是可以通过类名直接调用这个方法,而减号则要实例化逸个对象,然后通过实例化的对象来调用该方法!! 2.(返回类型) 方法名 :(参数类型)变量名 空格 参数二名 :(参数类型) 变量名 空格 ...
- Java XML 序列化和反序列化
Utils 类: import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileWr ...
- 苹果内购服务器验证之receipt返回多组in_app思考
最近有部分用户反映,苹果内购充值失败,经过测试总结有几个关键点出现问题 1.app购买成功苹果没有返回票据,属于票据遗漏(取决于苹果服务器的响应状况),只能客户端进行监听刷新等处理 2.app连续购买 ...