天天看點

ABP領域層知識回顧之---工作單元

1. 前言

在上一篇博文中(http://www.cnblogs.com/xiyin/p/6842958.html) 我們講到了ABP領域層的倉儲。這邊博文我們來講 工作單元。個人覺得比較重要。文章的大緻結構分為六部分,如下圖所示:

ABP領域層知識回顧之---工作單元

2. 工作單元

2.1 通用連接配接和事務管理方法

.NET

使用連接配接池(

connection pooling

)。是以,建立一個連接配接實際上是從連接配接池中取得一個連接配接,因為建立新連接配接會有成本。如果沒有任何連接配接存在于連接配接池中, 一個新的連接配接對象會被建立并且添加到連接配接池中。當你釋放連接配接,它實際上是将這個連接配接對象送回到連接配接池。這并不是實際意義上的釋放。那麼,最佳實踐是什麼呢? 應該在使用完之後釋放掉連接配接對象。

那麼在應用程式中,建立和釋放連接配接的方法都有哪些呢?

  1. Web請求

    時,建立一個連接配接(

    Application_BeginRequest

    位于

    global.asax

    中的事件),使用同一個連接配接對象處理所有的資料庫操作,并且在請求結束的時候關閉/釋放這個連接配接(

    Application_EndRequest

    事件)。
  2. 建立一個連接配接當需要的時候(隻要在使用它之前)并且釋放它在使用它之後。

    上面兩個方法的優缺點呢?第一種簡單卻效率低下,為何?

    • 可能這個

      Web請求

      不需要操作資料庫,但是連接配接卻會開啟。這對于連接配接池來說是個毫無效率的使用方式。
    • 會讓

      Web請求

      的運作時間變長,并且資料庫操作還會需要一些執行。
    • 同樣的這是一個使用事務式的資料庫操作的最佳場景。如果又一個操作發生失敗,所有的操作都會會滾。因為事務會鎖住資料庫中的一些資料列(事件資料表),它必定是短暫的。
    第二種雖然高效,但是你需要不斷的進行建立和釋放連接配接操作。

2.2 ABP的連接配接和事務管理

上面講了兩個連接配接管理的方法,那麼ABP的連接配接管理方法又是怎麼樣的呢?ABP結合上面兩個連接配接管理的方法。如下:

2.2.1 倉儲類

倉儲是主要的資料庫操作的類。ABP開啟了一個資料庫連接配接并且在進入到倉儲方法時會啟用一個事務。是以, 你可以安全地使用連接配接于倉儲方法中。在倉儲方法結束後,事務會被送出并且釋放掉連接配接。例如倉儲方法抛出任何異常,事務會被會滾并且釋放掉連接配接。在這個模式中,倉儲方法是單元性的(相當于一個工作單元

unit of work

)。

當倉儲方法調用另一個倉儲方法(一般來說,若工作單元方法調用另一個工作單元方法),都使用同一個連接配接和事務。第一個被調用到的倉儲方法負責管理連接配接和事務,而其餘被它調用的倉儲方法則隻當初使用,不管理。

2.2.2 應用服務

一個應用服務的方法也被考慮使用工作單元。例:

public class TestService : ITestService
{
  private readonly ITestRepository _testRepository;
  private readonly IStaticRepository _staticRepository;
  
  public TestService(ITestRepository testRepository,
                    IStaticRepository staticRepository)
  {
     _testRepository = testRepository;
     _staticRepository = staticRepository;
  }
  
  public void CreateTestDemo(TestDemo input)
  {
    var test = new Test{ Name = "Robert" ,Age ="111"};
    _testRepository = testRepository;
    _staticRepository = staticRepository;
  }
}
           

CreateTestDemo

方法中,新增一個

Test

實體使用了

Test

倉儲和

Static

倉儲。兩個倉儲共享同一個連接配接和事務。ABP開啟一個資料庫連接配接并且開啟一個事務于進入到

CreateTestDemo

前 ,若沒有任何異常抛出,接着送出這個事務于方法結尾時。若有異常抛出,則會復原這個事務。在這種機制下,所有資料庫操作都成了單元性的。

2.2.3 工作單元

工作單元在背景替倉儲和惡應用服務的方法工作,加入你想要控制資料庫的連接配接和事務,你就需要直接操作工作單元。

1.示例一:

[UnitOFWork]
public void CreateTest(Test input){
  var test = new Test {Name = "Robert", Age=111 };
  _testRepository.Insert(test);
  _staticRepostiory.IncreaseAge();
}
           

CreateTest

方法轉變成工作單元并且管理資料庫連接配接和事務,兩個倉儲對象都使用相同的工作單元。如果是應用服務的方法,則不需要添加 UnitOfWork 屬性

2.3 工作單元

2.3.1 禁用工作單元

你或許想要禁用應用服務方法的工作單元(預設是開啟的),隻需在屬性上設定

IsDisable

屬性即可:

[UnitOfWork(IsDiable = true)]
public virtual void Remove(Test input){
  _removeRepository.Remove(input.Id);
}
           

平常,一般不需要這樣做。因為應用服務的方法都應該是單元性的,并且使用資料庫。

2.3.2 無事務的工作單元

工作單元預設是具有事務性的,是以,ABP啟動/送出/復原一個顯性的資料庫等級的事務。在有些特殊案例中,事務可能會導緻問題。因為它可能會鎖住有些資料列或是資料表于資料庫中。在此這些情景下,你或許會想要禁用資料庫等級的事務。

UnitOfWork

屬性可以如下寫法,取得一個布爾值來讓他的如非事務型工作單元工作。

[UnitOfWork(false)]
public GetTasksOutput GetTasks(GetTaskInput input){
  var tasks = _taskRepository.GetAllWithPeople(input.Id ,input.Age);
  return new GetTasksOutput{
    Tasks = Mapper.Map<List<TaskDto>>(tasks)
  };
}
           

[UnitOfWork(false)]

等同于

[UnitOfWork(isTransaction:false)]

如果你已經位于事務性工作單元區域内,設定

isTransaction

false

這個動作會被忽略。

2.3.3 工作單元調用其他工作單元

若工作單元方法(一個有

UnitOfWork

屬性的方法)調用另一個工作單元方法,他們共享同一個連接配接和事務。第一個方法管理連接配接,其他的方法隻是使用它。這在所有方法都執行在同一個線程下是可行的(或者是在同一個

Web請求

中)。實際上,當工作單元區域開始,,所有的程式代碼都會在同一個線程中執行并共享同一個連接配接事務, 直到工作單元區域終止。這對于使用

UnitOfWork

屬性和

UnitOfWorkScope

類來說都是一樣的。如果你建立了一個不同的線程/任務,它使用自己所屬的工作單元。

2.3.4 自動化的saving changes

當我們使用工作單元到方法上,ABP 自動的儲存所有變化于方法的末端。假設我們需要一個可更新

person

名稱的方法:

[UnitOfWork]
 public void UpdateName(UpdateNameInput input) {       
   var person = _personRepository.Get(input.PersonId); 
   person.Name = input.NewName;    
 } 
           

這樣,名稱就被修改了。

ORM 架構

會持續追蹤實體所有的變化于工作單元内,且反映所有變化到資料庫中。

2.3.5 倉儲接口的GetAll 方法

你在倉儲方法外調用

GetAll

方法, 這必定得有一個開啟狀态的資料庫連接配接,因為它傳回

IQueryable

類型的對象。這是需要的,因為

IQueryable

延遲執行。它并不會馬上執行資料庫查詢,直到你調用

ToList()

方法或在

foreach

循環中使用

IQueryable

(或是存取被查詢結果集的情況下)。 是以,當你調用

ToList()

方法,資料庫連接配接必需是啟用狀态。

GetAll()

方法,如果需要資料庫連接配接且沒有倉儲的情況下,你就必須要使用工作單元。注意,應用服務方法預設就是工作單元。

2.3.6 工作單元屬性的限制

你可以在下面的情景下使用

UnitOfWork

屬性标簽。

  • 類所有

    public

    public virtual

    這些基于界面的方法(就像是應用服務基本服務界面)
  • 自我注入類的

    public virtual

    方法(像是

    MVC Controller

    WebApi Controller

  • 所有

    protected virtual

    方法
建議将方法标示為

virtual

。你無法應用在

private

方法上。因為,ABP使用

dynamic proxy

來實作,而私有方法就無法使用繼承的方法來實作。當你不使用依賴注入且自行化初始化類,那麼

UnitOfWork

屬性就無法正常運作。

2.4 選項

我們可以在

startup configuration

中改變左右工作單元的所有預設值。這通常是用了我們子產品中的

PreInitialize

方法來實作。

public class SimpleTaskSystemCoreMudule : AbpModule{
  public override void PreInitialize(){
    Configuration.UnitOfWork.IsolationLevel = IsolationLEvel.ReadCommitted;
    Configuration.UnitOfWork.Timeout = TimeSpan.FromMinutes(30);
  }
}
           

2.5 方法

工作單元系統運作是無縫且不可視的。但是,在有些特例下, 你需要調用它的方法。

SaveChanges

ABP儲存所有的變化于工作單元的尾端,你不需要做任何事情。但是,有些時候,你或許會想要在工作單元的過程中就儲存所有變化。在這個案例中,你可以注入

IUnitOfWorkManager

并且調用

IUnitOfWorkManager.Current.SaveChanges()

方法。

2.6 事件

工作單元具有

Completed/Failed/Disposed

事件。 你可以注冊這些事件并且進行所需的操作。注入

IUnitOfWorkManager

并且使用

IUnitOfWorkManager.Current

屬性來取得目前已激活的工作單元并且注冊它的事件。

你或許會想要執行有些程式代碼于目前工作單元成功地完成。示例:

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) =>{
      //....
    };
  }
  _taskRepository.Insert(task);
}
           

繼續閱讀