ABP官方文檔翻譯 3.6 工作單元
工作單元
- 介紹
- ABP中的連接和事務管理
- 傳統的工作單元方法
- 控制工作單元
- UnitOfWork特性
- IUnitOfWorkManager
- 工作單元詳情
- 禁用工作單元
- 無事務工作單元
- 一個工作單元方法調用另一個
- 工作單元範圍
- 自動保存更改
- IRepository.GetAll()方法
- 工作單元特性限制
- 選項
- 方法
- SaveChanges
- 事件
介紹
在使用數據庫的應用中,連接和事務管理是最重要的概念之一。什麽時候打開連接,什麽時候開始一個事務,如何釋放連接等等。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); }
返回主目錄
ABP官方文檔翻譯 3.6 工作單元