[UnitOfWork] public void CreatePerson(CreatePersonInput input) { var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress }; _personRepository.Insert(person); _statisticsRepository.IncrementPeopleCount(); }
因此,CreatePerson方法转变成工作单元并且管理数据库连接和事务,两个仓储对象都使用相同的工作单元。要注意,假如这是应用服务的方法则不需要添加UnitOfWork属性,见工作单元方法:第三章,3.3.5。
第二个示例是使用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,如上所示。因此,你可以创建更多的有限范围 (limited scope)的工作单元。在这个机制中,你通常可以手动调用Complete方法。如果你不调用,事务会回滚并且所有的异常都不会被储存。Begin方法被重写从而设置工作单元的选项。
这很棒,不过除非你有很好的理由,否则还是少用UnitOfWork属性。
工作单元
1.禁用工作单元(Disabling unit of work)
你或许会想要禁用应用服务方法的工作单元(因为它默认是启用的)。要想做到这个,使用UnitOfWorkAttribute的IsDisabled属性。示例如下:
[UnitOfWork(IsDisabled = true)] public virtual void RemoveFriendship(RemoveFriendshipInput input) { _friendshipRepository.Delete(input.Id); }
平常时, 你不会需要这么做,这是因为应用服务的方法都应该是单元性且通常是使用数据库。在有些情况下,你或许会想要禁用应用服务的工作单元:
(1)你的方法不需要任何数据库操作且你不想要开启那些不需要的数据库连接
(2)你想要使用工作单元于UnitOfWorkScope类的有限范围内,如上所述
注意,如果工作单元方法调用这个RemoveFriendship方法,禁用被忽略且它和调用它的方法使用同一个工作单元。因此,使用禁用这个功能要很小心。同样地,上述程序代码工作的很好,因为仓储方法默认即为工作单元。
2.无事务的工作单元(Non-transactional unit of work)
工作单元默认上是具事务性的(这是它的天性)。因此,ABP启动/提交/回滚一个显性的数据库等级的事务。在有些特殊案例中,事务可能会导致问题,因为它可能会锁住有些数据列或是数据表于数据库中。在此这些情境下, 你或许会想要禁用数据库等级的事务。UnitOfWork属性可以从它的建构子中取得一个布尔值来让它如非事务型工作单元般工作着。示例为:
[UnitOfWork(false)] public GetTasksOutput GetTasks(GetTasksInput input) { var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State); return new GetTasksOutput { Tasks = Mapper.Map<List<TaskDto>>(tasks) }; }
建议可以这么做[UnitOfWork(isTransaction:false)]。(具有可读性并且明确)。
注意,ORM框架(像是NHibernate和EntityFramework)会在单一命令中于内部进行数据储存。假设你更新了一些的实体于非事务的UoW。即便于这个情境下所有的更新都会于单一数据库命令的工作单元尾部完成。但是,如果你直接执行SQL查询,它会立即被执行。
这里有一个非事务性UoW的限制。如果你已经位于事务性UoW区域内,设定isTransactional为false这个动作会被忽略。
使用非事务性UoW要小心,因为在大多数的情况下,数据整合应该是具事务性的。如果你的方法只是读取数据,不改变数据,那么当然可以采用非事务性。
3.工作单元调用其它工作单元(A unit of work method calls another)
若工作单元方法(一个贴上UnitOfWork属性标签的方法)调用另一个工作单元方法,他们共享同一个连接和事务。第一个方法管理连接,其它的方法只是使用它。这在所有方法都执行在同一个线程下是可行的(或是在同一个Web请求内)。实际上,当工作单元区域开始,所有的程序代码都会在同一个线程中执行并共享同一个连接事务,直到工作单元区域终止。这对于使用UnitOfWork属性和UnitOfWorkScope类来说都是一样的。如果你创建了一个不同的线程/任务,它使用自己所属的工作单元。
自动化的saving changes (Automatically saving changes)
当我们使用工作单元到方法上,ABP自动的储存所有变化于方法的末端。假设我们需要一个可更新person名称的方法:
[UnitOfWork] public void UpdateName(UpdateNameInput input) { var person = _personRepository.Get(input.PersonId); person.Name = input.NewName; }
就这样,名称就被修改了!我们甚至没有调用_personRepository.Update方法。ORM框架会持续追踪实体所有的变化于工作单元内,且反映所有变化到数据库中。
注意,这不需要在应用服务声明UnitOfWork,因为它们默认就是采用工作单元。
4.仓储接口的GetAll()方法(IRepository.GetAll())