文档目录

本节内容:

简介

在一个使用数据库的应用里,连接和事务是非常重要的,何时打开一个连接,何时开启一个事务,如果释放连接等等。ABP通过工作单元系统管理数据库的连接和事务。

在ABP中管理连接和事务

当进行一个工作单元方法,ABP打开一个数据库连接(可能不是马上打开,在第一次用到数据库时打开,这要看ORM供应器的实现了)和开始一个事务,所以你可以安全地在这个方法内使用连接,在这个方法的最后,提交事务并释放连接,如果这个方法抛出任何的异常,回滚事务并释放连接,这种方式,一个工作单元就是原子性的。ABP自动完成这些操作。

如果一个工作单元方法调用另一个工作单元方法,两个会使用同一个连接和事务,第一个方法管理连接和事务,另一个使用它们。

约定的工作单元方法

有些方法默认为工作单元方法:

假设我们有一个应用服务方法,如下所示:

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框架 - 工作单元的更多相关文章

  1. 手工搭建基于ABP的框架 - 工作单元以及事务管理

    一个业务功能往往不只由一次数据库请求(或者服务调用)实现.为了功能的完整性,我们希望如果该功能执行一半时出错,则撤销前面已执行的改动.在数据库层面上,事务管理实现了这种完整性需求.在ABP中,一个完整 ...

  2. ABP的工作单元

    http://www.aspnetboilerplate.com/Pages/Documents/Unit-Of-Work 工作单元位于领域层.   ABP的数据库连接和事务处理: 1,仓储类 ASP ...

  3. Abp之工作单元与事务

    环境:Abp1.2 疑问:没有调用工作单元的SaveChanges方法引起的事务提交时机的问题. 例如:有一个应用服务代码如下: public void CreatePhrase(PhraseCrea ...

  4. 【ABP】工作单元——不进行事物独立执行功能

    1.注入 private readonly IUnitOfWorkManager unitOfWorkManager; 2.构造 3.开启新事物 using (var unitOfWork = uni ...

  5. ABP官方文档翻译 3.6 工作单元

    工作单元 介绍 ABP中的连接和事务管理 传统的工作单元方法 控制工作单元 UnitOfWork特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 无事务工作单元 一个工作单元方法 ...

  6. EntityFrameworkCore之工作单元的封装

    1. 简介 2. DbContext 生命周期和使用规范 2.1. 生命周期 2.2. 使用规范 2.3. 避免 DbContext 线程处理问题 3. 封装-工作单元 3.1. 分析 3.2. 设计 ...

  7. 解析ABP框架中的事务处理和工作单元,ABP事务处理

    通用连接和事务管理方法连接和事务管理是使用数据库的应用程序最重要的概念之一.当你开启一个数据库连接,什么时候开始事务,如何释放连接...诸如此类的. 正如大家都知道的,.Net使用连接池(connec ...

  8. [ABP]浅谈工作单元 在整个 ABP 框架当中的应用

    ABP在其内部实现了工作单元模式,统一地进行事务与连接管理. 其核心就是通过 Castle 的 Dynamic Proxy 进行动态代理,在组件注册的时候进行拦截器注入,拦截到实现了 Unit Of ...

  9. 浅谈工作单元 在整个 ABP 框架当中的应用

    ABP在其内部实现了工作单元模式,统一地进行事务与连接管理. 其核心就是通过 Castle 的 Dynamic Proxy 进行动态代理,在组件注册的时候进行拦截器注入,拦截到实现了 Unit Of ...

随机推荐

  1. CSS3 border-radius边框圆角

    在CSS3中提供了对边框进行圆角设定的支持,可对边框1~4个角进行圆角样式设置. 目录 1. 介绍 2. value值的格式和类型 3. border-radius 1~4个参数说明 4. 在线示例 ...

  2. C语言 · 数位分离

    问题描述 编写一个程序,输入一个1000 以内的正整数,然后把这个整数的每一位数字都分离出来,并逐一地显示. 输入格式:输入只有一行,即一个1000以内的正整数. 输出格式:输出只有一行,即该整数的每 ...

  3. 构建通用的 React 和 Node 应用

    这是一篇非常优秀的 React 教程,这篇文章对 React 组件.React Router 以及 Node 做了很好的梳理.我是 9 月份读的该文章,当时跟着教程做了一遍,收获很大.但是由于时间原因 ...

  4. node-sass 安装失败的解决措施

    在测试gulp-webapp的时候遇到了styles不能被正常编译的问题,究其原因是node-sass没有被正常安装. 根本原因是国内网络的原因. 最终的解决方法是通过淘宝的npm镜像安装node-s ...

  5. 谈谈一些有趣的CSS题目(八)-- 纯CSS的导航栏Tab切换方案

    开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...

  6. [C#] 了解过入口函数 Main() 吗?带你用批处理玩转 Main 函数

    了解过入口函数 Main() 吗?带你用批处理玩转 Main 函数 目录 简介 特点 方法的参数 方法的返回值 与批处理交互的一个示例 简介 我们知道,新建一个控制台应用程序的时候,IDE 会同时创建 ...

  7. spring源码分析之@ImportSelector、@Import、ImportResource工作原理分析

    1. @importSelector定义: /** * Interface to be implemented by types that determine which @{@link Config ...

  8. Kooboo CMS技术文档之一:Kooboo CMS技术背景

    语言平台 依赖注入方案 存储模型 1. 语言平台 Kooboo CMS基于.NET Framework 4.x,.NET Framework 4.x的一些技术特性成为站点开发人员使用Kooboo CM ...

  9. AI人工智能系列随笔:syntaxnet 初探(1)

    人工智能是 最近的一个比较火的名词,相信大家对于阿尔法狗都不陌生吧?其实我对人工智能以前也是非常抵触的,因为我认为机器人会取代人类,成为地球乃至宇宙的霸主,但是人工智能带给我的这种冲击,我个人感觉是欲 ...

  10. 餐饮连锁公司IT信息化解决方案一

             从餐饮企业的信息化需求来说,没有哪一种解决方案能满足所有餐饮企业的信息化建设需要.不同的餐饮业态有不同的业务流程,不同业态的信息化解决方案是目前餐饮企业信息化建设急需的,这种一站式整 ...