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(30);
    }

    //...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);
}

返回主目录

时间: 2024-10-22 22:53:07

ABP官方文档翻译 3.6 工作单元的相关文章

ABP官方文档翻译 9.1 EntityFramework集成

EntityFramework集成 Nuget包 DbContext 仓储 默认仓储 自定义仓储 应用特定的基础仓储类 自定义仓储示例 仓储最佳实践 事务管理 数据存储 ABP可以使用ORM框架,它内置集成EntityFramework.本文档将讲解ABP如何使用EntityFramework.假定你对EntityFramework已经有了初级水平. Nuget包 在ABP中使用Abp.EntityFramework nuget包扩展了EntityFramework.需要将它添加到工程中.最好在

ABP官方文档翻译 3.3 仓储

 仓储 默认仓储 自定义仓储 自定义仓储接口 自定义仓储实现 基础仓储方法管理数据库连接 查询 获取单个实体 获取实体列表 关于IQueryable 自定义返回值 插入 更新 删除 其他 关于异步方法 管理数据库连接 仓储生命周期 仓储最佳实践 协调领域和数据映射层,使用类集合接口访问领域对象."(Martin Fowler) 实际上,仓储用来执行领域对象的数据库操作(实体和值类型).通常,每个对象(或聚合根)使用单独的仓储. 默认仓储 在ABP中,仓储类实现IRepository<TEn

ABP官方文档翻译 6.2.1 ASP.NET Core集成

ASP.NET Core 介绍 迁移到ASP.NET Core? 启动模板 配置 启动类 模块配置 控制器 应用服务作为控制器 过滤器 授权过滤器 审计Action过滤器 校验过滤器 工作单元Action过滤器 异常过滤器 结果过滤器 Ajax请求的结果缓存 模型绑定器 视图 客户端代理 集成测试 介绍 本文档描述了ABP如何集成ASP.NET Core.ASP.NET Core通过Abp.AspNetCore nuget包实现集成. 迁移到ASP.NET Core? 如果你已经有一个工程并考虑

ABP官方文档翻译 4.1 应用服务

应用服务 IApplicationService接口 ApplicationService类 CrudService和AsyncCrudAppService类 简单的CRUD应用服务示例 自定义CRUD应用服务 GettingList Create和Update 其他方法参数 CRUD权限 工作单元 应用服务生命周期 应用服务将领域逻辑暴露给展示层.在展示层使用DTO(数据传输对象)作为参数调用应用服务,应用服务使用领域对象执行一些特定的业务逻辑,并返回DTO到展示层.因此,展示层与领域层是完全

ABP官方文档翻译 1.2 N层架构

N层架构 介绍 ABP架构 其他(通用) 领域层 应用层 基础设施层 网络和展现层 其他 总结 介绍 应用程序代码库的分层架构是被广泛认可的可以减少程序复杂度.提高代码复用率的技术.为了实现分层架构,ABP遵循领域驱动设计的原则.在领域驱动设计中有四个基本层: 表现层:提供用户接口.使用应用层实现用户交互. 应用层:桥接表现层和领域层.协调业务对象来执行特定的应用任务. 领域层:包括业务对象以及业务规则.此层是整个应用的核心. 基础设施层:提供通用的技术能力来支持高层.基础设施层可以是使用ORM

ABP官方文档翻译 3.1 实体

实体 实体类 聚合根类 领域事件 常规接口 审计 软删除 激活/失活实体 实体改变事件 IEntity接口 实体是DDD(领域驱动设计)的核心概念之一.Eric Evans描述它为"An object that is not fundamentally defined by its attributes, but rather by a thread of continuity and identity."所以,实体有Id并存储在数据库.实体一般映射到关系库中的表. 实体类 在ABP,

ABP官方文档翻译 5.1 Web API控制器

ASP.NET Web API控制器 介绍 AbpApiController基类 本地化 其他 过滤器 审计日志 授权 反伪造过滤器 工作单元 结果包装和异常处理 结果缓存 校验 模型绑定器 介绍 ABP通过Abp.Web.Api nuget包集成到ASP.NET Web API控制器.你可以按照往常一样创建ASP.NET Web API控制器.依赖注入系统可以用于一般的ApiControllers.但是,建议继承AbpApiController,它提供了许多好处并且能够更好的与ABP集成. A

ABP官方文档翻译 3.8 数据过滤器

数据顾虑器 介绍 预定义过滤器 ISoftDelete 何时使用? IMustHaveTenant 何时使用? IMayHaveTenant 何时使用 禁用过滤器 关于using语句 关于多租户 全局禁用过滤器 启用过滤器 设置过滤器参数 SetTenantId方法 ORM集成 Entity Framework EntityFramework.DynaamicFilters文档 其他ORMs 介绍 软删除模式是常用的模式,这种模式并没有从数据库中删除实体而是打上'deleted'的标记.所以,如

ABP官方文档翻译 10.1 ABP Nuget包

ABP Nuget包 Packages Abp Abp.AspNetCore Abp.Web.Common Abp.Web Abp.Web.Mvc Abp.Web.Api Abp.Web.Api.OData Abp.Web.Resources Abp.Web.SignalR Abp.Owin Abp.EntityFramework.Common Abp.EntityFramework Abp.EntityFramework.GraphDiff Abp.EntityFrameworkCore Ab