解析ABP框架中的事務(wù)處理和工作單元
來源:易賢網(wǎng) 閱讀:1713 次 日期:2016-08-06 13:38:06
溫馨提示:易賢網(wǎng)小編為您整理了“解析ABP框架中的事務(wù)處理和工作單元”,方便廣大網(wǎng)友查閱!

ABP是"ASP.NET Boilerplate Project(ASP.NET樣板項目)"的簡稱,是一個建立在.NET之上的Web開發(fā)框架,下面我們來解析ABP框架中的事務(wù)處理和工作單元

通用連接和事務(wù)管理方法:

連接和事務(wù)管理是使用數(shù)據(jù)庫的應(yīng)用程序最重要的概念之一。當(dāng)你開啟一個數(shù)據(jù)庫連接,什么時候開始事務(wù),如何釋放連接...諸如此類的。

正如大家都知道的,.Net使用連接池(connection pooling)。因此,創(chuàng)建一個連接實際上是從連接池中取得一個連接,會這么做是因為創(chuàng)建新連接會有成本。如果沒有任何連接存在于連接池中,一個新的連接對象會被創(chuàng)建并且添加到連接池中。當(dāng)你釋放連接,它實際上是將這個連接對象送回到連接池。這并不是實際意義上的釋放。這個機制是由.Net所提供的。因此,我們應(yīng)該在使用完之后釋放掉連接對象。這就是最佳實踐。

在應(yīng)用程序中,有兩個通用的方來創(chuàng)建/釋放一個數(shù)據(jù)庫連接:

第一個方法:在Web請求到達(dá)的時候,創(chuàng)建一個連接對象。(Application_BeginRequest這個位于global.asax中的事件),使用同一個連接對象來處理所有的數(shù)據(jù)庫操作,并且在請求結(jié)束的時候關(guān)閉/釋放這個連接 (Application_EndRequest事件)。

這是個簡易但卻沒效率的方法,原因:

或許這個Web請求不需要操作數(shù)據(jù)庫,但是連接卻會開啟。這對于連接池來說是個毫無效率的使用方式。

這可能會讓W(xué)eb請求的運行時間變長,并且數(shù)據(jù)庫操作還會需要一些執(zhí)行。這也是一種沒效率的連接池使用方式。

這對于Web應(yīng)用來說是可行的。如果你的應(yīng)用程序是Widnows Service,這可能就無法被實現(xiàn)了。

同樣的這是一個使用事務(wù)式的數(shù)據(jù)庫操作最佳場景。如果有一個操作發(fā)生失敗,所有的操作都會回滾。因為事務(wù)會鎖住數(shù)據(jù)庫中的一些數(shù)據(jù)列(事件數(shù)據(jù)表),它必定要是短暫的。

第二個方法: 創(chuàng)建一個連接當(dāng)需要的時候(只要在使用它之前)并且釋放它在使用它之后。這是相當(dāng)高效的,但是就得乏味而且反復(fù)的去進(jìn)行(創(chuàng)建/釋放連接)。

ABP的連接和事務(wù)管理

ABP綜合上述兩個連接管理的方法,并且提供一個簡單而且高效的模型。

1.倉儲類(Repository classes)

倉儲是主要的數(shù)據(jù)庫操作的類。ABP開啟了一個數(shù)據(jù)庫連接并且在進(jìn)入到倉儲方法時會啟用一個事務(wù)。因此,你可以安全地使用連接于倉儲方法中。在倉儲方法結(jié)束后,事務(wù)會被提交并且會釋放掉連接。假如倉儲方法拋出任何異常,事務(wù)會被回滾并且釋放掉連接。在這個模式中,倉儲方法是單元性的(一個工作單元unit of work)。ABP在處理上述那些動作都是全自動的。在這里,有一個簡單的倉儲:

public class ContentRepository : NhRepositoryBase<Content>, IContentRepository

{

  public List<Content> GetActiveContents(string searchCondition)

  {

    var query = from content in Session.Query<Content>()

          where content.IsActive && !content.IsDeleted

          select content;

    if (string.IsNullOrEmpty(searchCondition))

    {

      query = query.Where(content => content.Text.Contains(searchCondition));

    }

    return query.ToList();

  }

}

這個示例使用NHibernate作為ORM框架。如上所示,不需要撰寫任何數(shù)據(jù)庫連接操作(NHibernate中的Session)的程序代碼。

假如倉儲方法調(diào)用另一個倉儲方法(一般來說,若工作單元方法調(diào)用另一個工作單元的方法),都使用同一個連接和事務(wù)。第一個被調(diào)用到的倉儲方法負(fù)責(zé)管理連接和事務(wù),而其余被它調(diào)用的倉儲方法則只單純使用不管理。

2.應(yīng)用服務(wù)(Application service classes)

一個應(yīng)用服務(wù)的方法也被考慮使用工作單元。如果我們擁有一個應(yīng)用服務(wù)方法如下:

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倉儲并且使用statistics倉儲增加總people數(shù)量。兩個倉儲共享同一個連接和事務(wù)于這個例子中,因為這是一個應(yīng)用服務(wù)的方法。ABP開啟一個數(shù)據(jù)庫連接并且開啟一個事務(wù)于進(jìn)入到CreationPerson這個方法,若沒有任何異常拋出,接著提交這個事務(wù)于方法結(jié)尾時,若有異常被拋出,則會回滾這個事務(wù)。在這種機制下,所有數(shù)據(jù)庫的操作在CreatePerson中,都成了單元性的了(工作單元)。

3.工作單元(Unit of work)

工作單元在后臺替?zhèn)}儲和應(yīng)用服務(wù)的方法工作。假如你想要控制數(shù)據(jù)庫的連接和事務(wù),你就需要直接操作工作單元。下面有兩個直接使用的示例:

首要且最好的使用UnitOfWorkAttribute的方式如下:

[UnitOfWork]

public void CreatePerson(CreatePersonInput input)

{

  var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };

  _personRepository.Insert(person);

  _statisticsRepository.IncrementPeopleCount();

}

因此,CreatePerson方法轉(zhuǎn)變成工作單元并且管理數(shù)據(jù)庫連接和事務(wù),兩個倉儲對象都使用相同的工作單元。要注意,假如這是應(yīng)用服務(wù)的方法則不需要添加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,如上所示。因此,你可以創(chuàng)建更多的有限范圍 (limited scope)的工作單元。在這個機制中,你通??梢允謩诱{(diào)用Complete方法。如果你不調(diào)用,事務(wù)會回滾并且所有的異常都不會被儲存。Begin方法被重寫從而設(shè)置工作單元的選項。

這很棒,不過除非你有很好的理由,否則還是少用UnitOfWork屬性。

工作單元

1.禁用工作單元(Disabling unit of work)

你或許會想要禁用應(yīng)用服務(wù)方法的工作單元(因為它默認(rèn)是啟用的)。要想做到這個,使用UnitOfWorkAttribute的IsDisabled屬性。示例如下:

[UnitOfWork(IsDisabled = true)]

public virtual void RemoveFriendship(RemoveFriendshipInput input)

{

  _friendshipRepository.Delete(input.Id);

}

 平常時, 你不會需要這么做,這是因為應(yīng)用服務(wù)的方法都應(yīng)該是單元性且通常是使用數(shù)據(jù)庫。在有些情況下,你或許會想要禁用應(yīng)用服務(wù)的工作單元:

(1)你的方法不需要任何數(shù)據(jù)庫操作且你不想要開啟那些不需要的數(shù)據(jù)庫連接

(2)你想要使用工作單元于UnitOfWorkScope類的有限范圍內(nèi),如上所述

注意,如果工作單元方法調(diào)用這個RemoveFriendship方法,禁用被忽略且它和調(diào)用它的方法使用同一個工作單元。因此,使用禁用這個功能要很小心。同樣地,上述程序代碼工作的很好,因為倉儲方法默認(rèn)即為工作單元。

2.無事務(wù)的工作單元(Non-transactional unit of work)

工作單元默認(rèn)上是具事務(wù)性的(這是它的天性)。因此,ABP啟動/提交/回滾一個顯性的數(shù)據(jù)庫等級的事務(wù)。在有些特殊案例中,事務(wù)可能會導(dǎo)致問題,因為它可能會鎖住有些數(shù)據(jù)列或是數(shù)據(jù)表于數(shù)據(jù)庫中。在此這些情境下, 你或許會想要禁用數(shù)據(jù)庫等級的事務(wù)。UnitOfWork屬性可以從它的建構(gòu)子中取得一個布爾值來讓它如非事務(wù)型工作單元般工作著。示例為:

[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)會在單一命令中于內(nèi)部進(jìn)行數(shù)據(jù)儲存。假設(shè)你更新了一些的實體于非事務(wù)的UoW。即便于這個情境下所有的更新都會于單一數(shù)據(jù)庫命令的工作單元尾部完成。但是,如果你直接執(zhí)行SQL查詢,它會立即被執(zhí)行。

這里有一個非事務(wù)性UoW的限制。如果你已經(jīng)位于事務(wù)性UoW區(qū)域內(nèi),設(shè)定isTransactional為false這個動作會被忽略。

使用非事務(wù)性UoW要小心,因為在大多數(shù)的情況下,數(shù)據(jù)整合應(yīng)該是具事務(wù)性的。如果你的方法只是讀取數(shù)據(jù),不改變數(shù)據(jù),那么當(dāng)然可以采用非事務(wù)性。

3.工作單元調(diào)用其它工作單元(A unit of work method calls another)

若工作單元方法(一個貼上UnitOfWork屬性標(biāo)簽的方法)調(diào)用另一個工作單元方法,他們共享同一個連接和事務(wù)。第一個方法管理連接,其它的方法只是使用它。這在所有方法都執(zhí)行在同一個線程下是可行的(或是在同一個Web請求內(nèi))。實際上,當(dāng)工作單元區(qū)域開始,所有的程序代碼都會在同一個線程中執(zhí)行并共享同一個連接事務(wù),直到工作單元區(qū)域終止。這對于使用UnitOfWork屬性和UnitOfWorkScope類來說都是一樣的。如果你創(chuàng)建了一個不同的線程/任務(wù),它使用自己所屬的工作單元。

自動化的saving changes (Automatically saving changes)

當(dāng)我們使用工作單元到方法上,ABP自動的儲存所有變化于方法的末端。假設(shè)我們需要一個可更新person名稱的方法:

[UnitOfWork]

 public void UpdateName(UpdateNameInput input) {

  var person = _personRepository.Get(input.PersonId);

  person.Name = input.NewName;

 }

就這樣,名稱就被修改了!我們甚至沒有調(diào)用_personRepository.Update方法。ORM框架會持續(xù)追蹤實體所有的變化于工作單元內(nèi),且反映所有變化到數(shù)據(jù)庫中。

注意,這不需要在應(yīng)用服務(wù)聲明UnitOfWork,因為它們默認(rèn)就是采用工作單元。

4.倉儲接口的GetAll()方法(IRepository.GetAll())

當(dāng)你在倉儲方法外調(diào)用GetAll方法, 這必定得有一個開啟狀態(tài)的數(shù)據(jù)庫連接,因為它返回IQueryable類型的對象。這是需要的,因為IQueryable延遲執(zhí)行。它并不會馬上執(zhí)行數(shù)據(jù)庫查詢,直到你調(diào)用ToList()方法或在foreach循環(huán)中使用IQueryable(或是存取被查詢結(jié)果集的情況下)。因此,當(dāng)你調(diào)用ToList()方法,數(shù)據(jù)庫連接必需是啟用狀態(tài)。示例:

[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在被調(diào)用ToList()方法于方法本體內(nèi),并且數(shù)據(jù)庫連接必須于IQueryable.ToList()被執(zhí)行時開啟。

一如GetAll()方法,如果需要數(shù)據(jù)庫連接且沒有倉儲的情況下,你就必須要使用工作單元。注意,應(yīng)用服務(wù)方法默認(rèn)就是工作單元。

5.工作單元屬性的限制(UnitOfWork attribute restrictions)

在下面情境下你可以使用UnitOfWork屬性標(biāo)簽:

(1)類所有public或public virtual這些基于界面的方法(像是應(yīng)用服務(wù)是基于服務(wù)界面)

(2)自我注入類的public virtual方法(像是MVC Controller和Web API Controller)

(3)所有protected virtual方法。

建議將方法標(biāo)示為virtual。你無法應(yīng)用在private方法上。因為,ABP使用dynamic proxy來實現(xiàn),而私有方法就無法使用繼承的方法來實現(xiàn)。當(dāng)你不使用依賴注入且自行初始化類,那么UnitOfWork屬性(以及任何代理)就無法正常運作。

選項

有許多可以用來控制工作單元的選項。

首先,我們可以在startup configuration中改變所有工作單元的所有默認(rèn)值。這通常是用了我們模塊中的PreInitialize方法來實現(xiàn)。

public class SimpleTaskSystemCoreModule : AbpModule

{

  public override void PreInitialize()

  {

    Configuration.UnitOfWork.IsolationLevel = IsolationLevel.ReadCommitted;

    Configuration.UnitOfWork.Timeout = TimeSpan.FromMinutes(30);

  }

  //...other module methods

}

方法

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

SaveChanges:

ABP儲存所有的變化于工作單元的尾端,你不需要做任何事情。但是,有些時候,你或許會想要在工作單元的過程中就儲存所有變化。在這個案例中,你可以注入IUnitOfWorkManager并且調(diào)用IUnitOfWorkManager.Current.SaveChanges()方法。示例中以Entity Framework在儲存變化時取得新增實體的Id。注意,當(dāng)前工作單元是具事務(wù)性的,所有在事務(wù)中的變化會在異常發(fā)生時都被回滾,即便是已調(diào)用SaveChange。

事件

工作單元具有Completed/Failed/Disposed事件。你可以注冊這些事件并且進(jìn)行所需的操作。注入IUnitOfWorkManager并且使用IUnitOfWorkManager.Current 屬性來取得當(dāng)前已激活的工作單元并且注冊它的事件。

你或許會想要執(zhí)行有些程序代碼于當(dāng)前工作單元成功地完成。示例:

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);

}

更多信息請查看網(wǎng)絡(luò)編程
易賢網(wǎng)手機網(wǎng)站地址:解析ABP框架中的事務(wù)處理和工作單元
由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢回復(fù)僅供參考,敬請考生以權(quán)威部門公布的正式信息和咨詢?yōu)闇?zhǔn)!
關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡要咨詢 | 簡要咨詢須知 | 加入群交流 | 手機站點 | 投訴建議
工業(yè)和信息化部備案號:滇ICP備2023014141號-1 云南省教育廳備案號:云教ICP備0901021 滇公網(wǎng)安備53010202001879號 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號
云南網(wǎng)警備案專用圖標(biāo)
聯(lián)系電話:0871-65317125(9:00—18:00) 獲取招聘考試信息及咨詢關(guān)注公眾號:hfpxwx
咨詢QQ:526150442(9:00—18:00)版權(quán)所有:易賢網(wǎng)
云南網(wǎng)警報警專用圖標(biāo)