工作单元

ABP框架的工作单元(UOW)实现提供了对应用程序中的数据库连接和事务范围的抽象和控制.

一旦一个新的UOW启动,它将创建一个环境作用域,当前作用域中执行的所有数据库操作都将参与该作用域并将其视为单个事务边界. 操作一起提交(成功时)或回滚(异常时).

ABP的UOW系统是;

  • 按约定工作, 所以大部分情况下你不需要处理UOW.
  • 数据库提供者独立.
  • Web独立, 这意味着你可以在Web应用程序/服务之外的任何类型的应用程序中创建工作单元作用域.

约定

以下方法类型被认为是一个工作单元:

  • ASP.NET Core MVC Controller Actions.
  • ASP.NET Core Razor Page Handlers.
  • 应用程序 方法.
  • 仓储方法.

UOW自动针对这些方法开始,除非**周围已经有一个(环境)**UOW在运行.示例;

  • 如果你调用一个仓储方法,但还没有启动UOW,它将自动启动一个新的事务UOW,其中包括在仓储方法中完成的所有操作,如果仓储方法没有抛出任何异常,则提交事务. 仓储方法根本不知道UOW或事务. 它只在一个常规的数据库对象上工作(例如用于EF CoreDbContext),而UOW由ABP框架处理.
  • 如果调用应用服务方法,则相同的UOW系统将按上述说明工作. 如果应用服务方法使用某些仓储,这些仓储不会开始新的UOW,而是参与由ABP框架为应用程序服务方法启动的当前工作单元中.
  • ASP.NET Core控制器操作也是如此. 如果操作以控制器action开始,UOW范围是控制器action的方法主体.

所有这些都是由ABP框架自动处理的.

数据库事务行为

虽然上一节解释了UOW是数据库事务,但实际上UOW不必是事务性的. 默认情况下;

  • HTTP GET请求不会启动事务性UOW. 它们仍然启动UOW,但不创建数据库事务.
  • 如果底层数据库提供程序支持数据库事务,那么所有其他HTTP请求类型都使用数据库事务启动UOW.

这是因为HTTP GET请求不会(也不应该)在数据库中进行任何更改. 你可以使用下面解释的选项来更改此行为.

默认选项

AbpUnitOfWorkDefaultOptions 用于配置工作单元系统的默认选项.在你的模块ConfigureServices 方法中配置选项.

示例: 完全禁用数据库事务

  1. Configure<AbpUnitOfWorkDefaultOptions>(options =>
  2. {
  3. options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
  4. });

选项属性

  • TransactionBehavior (enum: UnitOfWorkTransactionBehavior). 配置事务行为的全局点. 默认值为 Auto ,按照上面”*数据库事务行为”*一节的说明工作. 你可以使用此选项启用(甚至对于HTTP GET请求)或禁用事务.
  • TimeOut (int?): 用于设置UOW的超时值. 默认值是 null 并使用基础数据库提供程序的默认值.
  • IsolationLevel (IsolationLevel?): 如果UOW是事务性的用于设置数据库事务的隔离级别.

控制工作单元

在某些情况下你可能希望更改常规事务作用域,创建内部作用域或精细控制事务行为. 下面几节将介绍这些可能性.

IUnitOfWorkEnabled 接口

这是为不是按照上面解释的约定作为工作单元的类(或类的层次结构)启用UOW的一种简单方法.

示例: 为任意服务实现 IUnitOfWorkEnabled

  1. using System.Threading.Tasks;
  2. using Volo.Abp.DependencyInjection;
  3. using Volo.Abp.Uow;
  4. namespace AbpDemo
  5. {
  6. public class MyService : ITransientDependency, IUnitOfWorkEnabled
  7. {
  8. public virtual async Task FooAsync()
  9. {
  10. //this is a method with a UOW scope
  11. }
  12. }
  13. }

然后 MyService(和它的派生类)方法都将是UOW.

但是为了使它工作,有些规则应该被遵守;

  • 如果你不是通过接口(如IMyService)注入服务,则服务的方法必须是 virtual 的(否则动态代理/拦截系统将无法工作).
  • 仅异步方法(返回TaskTask<T>的方法)被拦截. 因此同步方法无法启动UOW.

注意,如果 FooAsync 在UOW作用域内被调用,那么它已经参与了UOW,不需要 IUnitOfWorkEnabled 或其他配置.

UnitOfWorkAttribute

UnitOfWork attribute提供了更多的可能性,比如启用或禁用UOW和控制事务行为.

UnitOfWork attribute可以用于方法级别.

示例: 为类的特定方法启用UOW

  1. using System.Threading.Tasks;
  2. using Volo.Abp.DependencyInjection;
  3. using Volo.Abp.Uow;
  4. namespace AbpDemo
  5. {
  6. public class MyService : ITransientDependency
  7. {
  8. [UnitOfWork]
  9. public virtual async Task FooAsync()
  10. {
  11. //this is a method with a UOW scope
  12. }
  13. public virtual async Task BarAsync()
  14. {
  15. //this is a method without UOW
  16. }
  17. }
  18. }

示例: 为类的所有方法启用UOW

  1. using System.Threading.Tasks;
  2. using Volo.Abp.DependencyInjection;
  3. using Volo.Abp.Uow;
  4. namespace AbpDemo
  5. {
  6. [UnitOfWork]
  7. public class MyService : ITransientDependency
  8. {
  9. public virtual async Task FooAsync()
  10. {
  11. //this is a method with a UOW scope
  12. }
  13. public virtual async Task BarAsync()
  14. {
  15. //this is a method with a UOW scope
  16. }
  17. }
  18. }

同样的规则也适用于此:

  • 如果你不是通过接口(如IMyService)注入服务,则服务的方法必须是 virtual 的(否则动态代理/拦截系统将无法工作).
  • 仅异步方法(返回TaskTask<T>的方法)被拦截. 因此同步方法无法启动UOW.

UnitOfWorkAttribute 属性

  • IsTransactional (bool?): 用于设置UOW是否是事务性的. 默认值为 null. 如果你让它为 null,它会通过约定和配置自动确定.
  • TimeOut (int?): 用于设置UOW的超时值. **默认值为 null**并回退到默认配置值.
  • IsolationLevel (IsolationLevel?): 如果UOW是事务的,用于设置数据库事务的隔离级别. 如果未设置,则使用默认值.
  • IsDisabled (bool): 用于禁用当前方法/类的UOW.

如果在环境UOW作用域内调用方法,将忽略 UnitOfWork 属性,并且该方法参与周围的事务.

示例: 为控制器action禁用UOW

  1. using System.Threading.Tasks;
  2. using Volo.Abp.AspNetCore.Mvc;
  3. using Volo.Abp.Uow;
  4. namespace AbpDemo.Web
  5. {
  6. public class MyController : AbpController
  7. {
  8. [UnitOfWork(IsDisabled = true)]
  9. public virtual async Task FooAsync()
  10. {
  11. //...
  12. }
  13. }
  14. }

IUnitOfWorkManager

IUnitOfWorkManager 是用于控制工作单元系统的主要服务. 下面的部分解释了如何使用此服务(大多数时候你并不需要).

开始新的工作单元

IUnitOfWorkManager.Begin 方法用于创建一个新的UOW作用域.

示例: 创建一个新的非事务性UOW作用域

  1. using System.Threading.Tasks;
  2. using Volo.Abp.DependencyInjection;
  3. using Volo.Abp.Uow;
  4. namespace AbpDemo
  5. {
  6. public class MyService : ITransientDependency
  7. {
  8. private readonly IUnitOfWorkManager _unitOfWorkManager;
  9. public MyService(IUnitOfWorkManager unitOfWorkManager)
  10. {
  11. _unitOfWorkManager = unitOfWorkManager;
  12. }
  13. public virtual async Task FooAsync()
  14. {
  15. using (var uow = _unitOfWorkManager.Begin(
  16. requiresNew: true, isTransactional: false
  17. ))
  18. {
  19. //...
  20. await uow.CompleteAsync();
  21. }
  22. }
  23. }
  24. }

Begin 方法有以下可选参数:

  • requiresNew (bool): 设置为 true 可忽略周围的工作单元,并使用提供的选项启动新的UOW. 默认值为false. 如果为false,并且周围有UOW,则 Begin 方法实际上不会开始新的UOW,而是以静默方式参与现有的UOW.
  • isTransactional (bool). 默认为 false.
  • isolationLevel (IsolationLevel?): 如果UOW是事务的,用于设置数据库事务的隔离级别. 如果未设置,则使用默认值.
  • TimeOut (int?): 用于设置UOW的超时值. **默认值为 null**并回退到默认配置值.

当前工作单元

如上所述UOW是环境的. 如果需要访问当前的工作单元,可以使用 IUnitOfWorkManager.Current 属性.

示例: 获取当前UOW

  1. using System.Threading.Tasks;
  2. using Volo.Abp.DependencyInjection;
  3. using Volo.Abp.Uow;
  4. namespace AbpDemo
  5. {
  6. public class MyProductService : ITransientDependency
  7. {
  8. private readonly IUnitOfWorkManager _unitOfWorkManager;
  9. public MyProductService(IUnitOfWorkManager unitOfWorkManager)
  10. {
  11. _unitOfWorkManager = unitOfWorkManager;
  12. }
  13. public async Task FooAsync()
  14. {
  15. var uow = _unitOfWorkManager.Current;
  16. //...
  17. }
  18. }
  19. }

Current 属性返回一个 IUnitOfWork 对象.

如果没有周围的工作单元,则当前工作单元可以为null. 如上所述,如果你的类是常规的UOW类,你将其手动设置为UOW或在UOW作用域内调用它,那么该值就不会为 null.

SaveChangesAsync

IUnitOfWork.SaveChangesAsync() 方法将到目前为止的所有更改保存到数据库中. 如果你正在使用EF Core,它的行为完全相同. 如果当前UOW是事务性的,即使已保存的更改也可以在错误时回滚(对于支持的数据库提供程序).

示例: 插入实体后保存更改以获取其自动增量ID

  1. using System.Threading.Tasks;
  2. using Volo.Abp.Application.Services;
  3. using Volo.Abp.Domain.Repositories;
  4. namespace AbpDemo
  5. {
  6. public class CategoryAppService : ApplicationService, ICategoryAppService
  7. {
  8. private readonly IRepository<Category, int> _categoryRepository;
  9. public CategoryAppService(IRepository<Category, int> categoryRepository)
  10. {
  11. _categoryRepository = categoryRepository;
  12. }
  13. public async Task<int> CreateAsync(string name)
  14. {
  15. var category = new Category {Name = name};
  16. await _categoryRepository.InsertAsync(category);
  17. //Saving changes to be able to get the auto increment id
  18. await UnitOfWorkManager.Current.SaveChangesAsync();
  19. return category.Id;
  20. }
  21. }
  22. }

示例的 Category 实体使用自动递增的 int 主键. 自动增量PK需要将实体保存到数据库中来获得新实体的ID.

示例是从基类 ApplicationService 派生的应用服务, IUnitOfWorkManager 服务已经作为 UnitOfWorkManager 属性注入,所以无需手动注入.

获取当前UOW非常常见,所以还有一个 UnitOfWorkManager.Current 的快捷属性 CurrentUnitOfWork. 所以可以对上面的例子进行以下更改:

  1. await CurrentUnitOfWork.SaveChangesAsync();
SaveChanges() 的替代方法

由于经常需要在插入,更新或删除实体后保存更改,相应的仓储方法有一个可选的 autoSave 参数. 可以将上面的 CreateAsync 方法按如下重写:

  1. public async Task<int> CreateAsync(string name)
  2. {
  3. var category = new Category {Name = name};
  4. await _categoryRepository.InsertAsync(category, autoSave: true);
  5. return category.Id;
  6. }

如果你的目的只是在创建/更新/删除实体后保存更改,建议你使用 autoSave 选项,而不是手动使用 CurrentUnitOfWork.SaveChangesAsync().

Note-1: 当工作单元结束而没有任何错误时,所有更改都会自动保存. 所以除非确实需要,否则不要调用 SaveChangesAsync() 和设置 autoSavetrue.

Note-2: 如果你使用 Guid 作为主键,则无需插入时保存来获取生成的id,因为 Guid 主键是在应用程序中设置的,创建新实体后立即可用.

IUnitOfWork 其他属性/方法

  • OnCompleted 方法获得一个回调动作,当工作单元成功完成时调用(在这里你可以确保所有更改都保存了).
  • FailedDisposed 事件可以用于UOW失败和被销毁的通知.
  • CompleteRollback 方法用于完成(提交)或回滚当前 UOW, 通常ABP框架在内部使用,如果你使用 IUnitOfWorkManager.Begin 方法手动启动事务,那么你可以手动使用这些方法.
  • Options 可用于获取启动UOW时使用的选项.
  • Items 字典可用于在同一工作单元内存储和获取任意对象,可以实现自定义逻辑.

ASP.NET Core 集成

工作单元系统已完全集成到ASP.NET Core. 它为UOW系统定义了动作过滤器和页面过滤器. 当你使用ASP.NET Core MVC控制器或Razor页面时,它可以正常工作.

使用ASP.NET Core时,通常你不需要做任何操作配置UOW.

工作单元中间件

AbpUnitOfWorkMiddleware 是可以在ASP.NET Core请求管道中启用UOW的中间件. 如果你需要扩大UOW范围以涵盖其他一些中间件,可以这样做.

示例:

  1. app.UseUnitOfWork();
  2. app.UseConfiguredEndpoints();