1. 程式人生 > >ABP(現代ASP.NET樣板開發框架)系列之11、ABP領域層——倉儲(Repositories)

ABP(現代ASP.NET樣板開發框架)系列之11、ABP領域層——倉儲(Repositories)

基於DDD的現代ASP.NET開發框架--ABP系列之11、ABP領域層——倉儲(Repositories)

ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。

本文由臺灣-小張提供翻譯

倉儲定義:“在領域層和資料對映層的中介,使用類似集合的介面來存取領域物件”(Martin Fowler)。

實際上,倉儲被用於領域物件在資料庫上的操作(實體Entity和值物件Value types)。一般來說,我們針對不同的實體(或聚合根Aggregate Root)會建立相對應的倉儲。

IRepository介面 

在ABP中,倉儲類要實現IRepository介面。最好的方式是針對不同倉儲物件定義各自不同的介面。

針對Person實體的倉儲介面宣告的示例如下所示:

public interface IPersonRepository : IRepository<Person> 
{
}

IPersonRepository繼承自IRepository<TEntity>,用來定義Id的型別為int(Int32)的實體。如果你的實體Id資料型別不是int,你可以繼承IRepository<TEntity, TPrimaryKey>介面,如下所示:

public interface IPersonRepository : IRepository<Person, long
>
{
}

對於倉儲類,IRepository定義了許多泛型的方法。比如: Select,Insert,Update,Delete方法(CRUD操作)。在大多數的時候,這些方法已足已應付一般實體的需要。如果這些方對於實體來說已足夠,我們便不需要再去建立這個實體所需的倉儲介面/類。在Implementation章節有更多細節。

(1)查詢(Query)

IRepository定義了從資料庫中檢索實體的常用方法。

取得單一實體(Getting single entity)

TEntity Get(TPrimaryKey id);
Task<TEntity> GetAsync(TPrimaryKey id);
TEntity Single(Expression
<Func<TEntity, bool>> predicate); TEntity FirstOrDefault(TPrimaryKey id); Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id); TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate); Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate); TEntity Load(TPrimaryKey id);

Get方法被用於根據主鍵值(Id)取得對應的實體。當資料庫中根據主鍵值找不到相符合的實體時,它會丟擲例外。Single方法類似Get方法,但是它的輸入引數是一個表示式而不是主鍵值(Id)。因此,我們可以寫Lambda表示式來取得實體。示例如下:

var  person = _personRepository.Get(42);
var  person = _personRepository.Single(p => o.Name == "Halil ibrahim Kalkan");

注意,Single方法會在給出的條件找不到實體或符合的實體超過一個以上時,都會丟擲例外。

FirstOrDefault也一樣,但是當沒有符合Lambda表示式或Id的實體時,會回傳null(取代丟擲異常)。當有超過一個以上的實體符合條件,它只會返回第一個實體。

Load並不會從資料庫中檢索實體,但它會建立延遲執行所需的代理物件。如果你只使用Id屬性,實際上並不會檢索實體,它只有在你存取想要查詢實體的某個屬性時才會從資料庫中查詢實體。當有效能需求的時候,這個方法可以用來替代Get方法。Load方法在NHibernate與ABP的整合中也有實現。如果ORM提供者(Provider)沒有實現這個方法,Load方法執行的會和Get方法一樣。

ABP有些方法具有非同步(Async)版本,可以應用在非同步開發模型上(見Async方法相關章節)。

取得實體列表(Getting list of entities)

List<TEntity> GetAllList();
Task<List<TEntity>> GetAllListAsync();
List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate);
Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate);
IQueryable<TEntity> GetAll();

GetAllList被用於從資料庫中檢索所有實體。過載並且提供過濾實體的功能,如下:

var  allPeople = _personRespository.GetAllList();
var  somePeople = _personRepository.GetAllList(person => person.IsActive && person.Age > 42);

GetAll返回IQueryable<T>型別的物件。因此我們可以在呼叫完這個方法之後進行Linq操作。示例:

//例子一
var  query = from person in _personRepository.GetAll()
where person.IsActive
orderby person.Name
select person;
var  people = query.ToList();

//例子二
List<Person> personList2 = _personRepository.GetAll().Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).Skip(40).Take(20).ToList();

如果呼叫GetAll方法,那麼幾乎所有查詢都可以使用Linq完成。甚至可以用它來編寫Join表示式。

說明:關於IQueryable<T>
當你呼叫GetAll這個方法在Repository物件以外的地方,必定會開啟資料庫連線。這是因為IQueryable<T>允許延遲執行。它會直到你呼叫ToList方法或在forEach迴圈上(或是一些存取已查詢的物件方法)使用IQueryable<T>時,才會實際執行資料庫的查詢。因此,當你呼叫ToList方法時,資料庫連線必需是啟用狀態。我們可以使用ABP所提供的UnitOfWork特性在呼叫的方法上來實現。注意,Application Service方法預設都已經是UnitOfWork。因此,使用了GetAll方法就不需要如同Application Service的方法上新增UnitOfWork特性。

有些方法擁有非同步版本,可應用在非同步開發模型(見關於async方法章節)。

自定義返回值(Custom return value)

ABP也有一個額外的方法來實現IQueryable<T>的延遲載入效果,而不需要在呼叫的方法上新增UnitOfWork這個屬性卷標。

T  Query<T>(Func<IQueryable<Tentity>,T> queryMethod);

查詢方法接受Lambda(或一個方法)來接收IQueryable<T>並且返回任何物件型別。示例如下:

var  people = _personRepository.Query(q => q.Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).ToList());

因為是採用Lambda(或方法)在倉儲物件的方法中執行,它會在資料庫連線開啟之後才被執行。你可以返回實體集合,或一個實體,或一個具部份欄位(注: 非Select *)或其它執行查詢後的查詢結果集。

(2)新增(insert)

IRepository介面定義了簡單的方法來提供新增一個實體到資料庫:

TEntity Insert(TEntity entity);
Task<TEntity> InsertAsync(TEntity entity);
TPrimaryKey InsertAndGetId(TEntity entity);
Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity);
TEntity InsertOrUpdate(TEntity entity);
Task<TEntity> InsertOrUpdateAsync(TEntity entity);
TPrimaryKey InsertOrUpdateAndGetId(TEntity entity);
Task<TPrimaryKey> InsertOrUpdateAndGetIdAsync(TEntity entity);

新增方法會新增實體到資料庫並且返回相同的已新增實體。InsertAndGetId方法返回新增實體的識別符號(Id)。當我們採用自動遞增識別符號值且需要取得實體的新產生識別符號值時非常好用。InsertOfUpdate會新增或更新實體,選擇那一種是根據Id是否有值來決定。最後,InsertOrUpdatedAndGetId會在實體被新增或更新後返回Id值。

所有的方法都擁有非同步版本可應用在非同步開發模型(見關於非同步方法章節)

(3)更新(UPDATE)

IRepository定義一個方法來實現更新一個已存在於資料庫中的實體。它更新實體並返回相同的實體物件。

TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity);

(4)刪除(Delete)

IRepository定了一些方法來刪除已存在資料庫中實體。

void Delete(TEntity entity);
Task DeleteAsync(TEntity entity);
void Delete(TPrimaryKey id);
Task DeleteAsync(TPrimaryKey id);
void Delete(Expression<Func<TEntity, bool>> predicate);
Task DeleteAsync(Expression<Func<TEntity, bool>> predicate);

第一個方法接受一個現存的實體,第二個方法接受現存實體的Id。

最後一個方法接受一個條件來刪除符合條件的實體。要注意,所有符合predicate表示式的實體會先被檢索而後刪除。因此,使用上要很小心,這是有可能造成許多問題,假如果有太多實體符合條件。

所有的方法都擁有async版本來應用在非同步開發模型(見關於非同步方法章節)。

(5)其它方法(others)

IRepository也提供一些方法來取得資料表中實體的數量。

int  Count();
Task<int> CountAsync();
int  Count(Expression<Func<TEntity, bool>> predicate);
Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate);
Long  LongCount();
Task<long> LongCountAsync();
Long  LongCount(Expression<Func<TEntity, bool>> predicate);
Task<long> LongCountAsync(Expression<TEntity, bool>> predicate);

所有的方法都擁有async版本被應用在非同步開發模型(見關於非同步方法章節)。

(6)關於非同步方法(About Async methods)

ABP支援非同步開發模型。因此,倉儲方法擁有Async版本。在這裡有一個使用非同步模型的application service方法的示例:

public class PersonAppService : AbpWpfDemoAppServiceBase, IPersonAppService
{
    private readonly IRepository<Person> _personRepository;

    public PersonAppService(IRepository<Person> personRepository)
    {
        _personRepository = personRepository;
    }

    public async Task<GetPeopleOutput> GetAllPeople()
    {
        var people = await _personRepository.GetAllListAsync();
            
        return new GetPeopleOutput
        {
            People = Mapper.Map<List<PersonDto>>(people)
        };
    }
}

GetAllPeople方法是非同步的並且使用GetAllListAsync與await保留關鍵字。

Async不是在每個ORM框架都有提供。

上例是從EF所提供的非同步能力。如果ORM框架沒有提供Async的倉儲方法則它會以同步的方式操作。同樣地,舉例來說,InsertAsync操作起來和EF的新增是一樣的,因為EF會直到單元作業(unit of work)完成之後才會寫入新實體到資料庫中(DbContext.SaveChanges)。

倉儲的實現

ABP在設計上是採取不指定特定ORM框架或其它存取資料庫技術的方式。只要實現IRepository介面,任何框架都可以使用。

倉儲要使用NHibernate或EF來實現都很簡單。見實現這些框架在ABP倉儲物件上一文:

  • NHibernate
  • EntityFramework

當你使用NHibernate或EntityFramework,如果提供的方法已足夠使用,你就不需要為你的實體建立倉儲物件了。我們可以直接注入IRepository<TEntity>(或IRepository<TEntity, TPrimaryKey>)。下面的示例為application service使用倉儲物件來新增實體到資料庫:

public class PersonAppService : IPersonAppService
{
    private readonly IRepository<Person> _personRepository;

    public PersonAppService(IRepository<Person> personRepository)
    {
        _personRepository = personRepository;
    }

    public void CreatePerson(CreatePersonInput input)
    {        
        person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
        
        _personRepository.Insert(person);
    }
}

PersonAppService的建構子注入了IRepository<Person>並且使用其Insert方法。當你有需要為實體建立一個客制的倉儲方法,那麼你就應該建立一個倉儲類給指定的實體。

管理資料庫連線

資料庫連線的開啟和關閉,在倉儲方法中,ABP會自動化的進行連線管理。

當倉儲方法被呼叫後,資料庫連線會自動開啟且啟動事務。當倉儲方法執行結束並且返回以後,所有的實體變化都會被儲存, 事務被提交併且資料庫連線被關閉,一切都由ABP自動化的控制。如果倉儲方法丟擲任何型別的異常,事務會自動地回滾並且資料連線會被關閉。上述所有操作在實現了IRepository介面的倉儲類所有公開的方法中都可以被呼叫。

如果倉儲方法呼叫其它倉儲方法(即便是不同倉儲的方法),它們共享同一個連線和事務。連線會由倉儲方法呼叫鏈最上層的那個倉儲方法所管理。更多關於資料庫管理,詳見UnitOfWork檔案。

儲的生命週期

所有的倉儲物件都是暫時性的。這就是說,它們是在有需要的時候才會被建立。ABP大量的使用依賴注入,當倉儲類需要被注入的時候,新的類實體會由注入容器會自動地建立。見相根據注入檔案有更多資訊。

倉儲的最佳實踐

  • 對於一個T型別的實體,是可以使用IRepository<T>。但別任何情況下都建立定製化的倉儲,除非我們真的很需要。預定義倉儲方法已經足夠應付各種案例。
  • 假如你正建立定製的倉儲(可以實現IRepository<TEntity>)
    • 倉儲類應該是無狀態的。這意味著, 你不該定義倉儲等級的狀態物件並且倉儲方法的呼叫也不應該影響到其它呼叫。    
    • 當倉儲可以使用相根據注入,儘可較少或是不相根據於其它服務。 

希望更多國內的架構師能關注到ABP這個專案,也許這其中有能幫助到您的地方,也許有您的參與,這個專案可以發展得更好。

歡迎加ABP架構設計交流QQ群:134710707

ABP架構設計交流群

相關推薦

ABP(現代ASP.NET樣板開發框架)系列11ABP領域——倉儲Repositories

基於DDD的現代ASP.NET開發框架--ABP系列之11、ABP領域層——倉儲(Repositories) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由臺灣-小張提供翻譯 倉儲定義:“在領域層和資料對映層的中

ABP(現代ASP.NET樣板開發框架)系列10ABP領域——實體

基於DDD的現代ASP.NET開發框架--ABP系列之10、ABP領域層——實體 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由深圳-Carl提供翻譯 實體是DDD(領域驅動設計)的核心概念之一。Eric Eva

ABP(現代ASP.NET樣板開發框架)系列4ABP模組系統

基於DDD的現代ASP.NET開發框架--ABP系列之4、ABP模組系統 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。  本文由東莞-天道提供翻譯  ABP模組系統簡介 ABP框架提供了建立和組裝模組的基礎,一個模組

ABP(現代ASP.NET樣板開發框架)系列6ABP依賴注入

基於DDD的現代ASP.NET開發框架--ABP系列之6、ABP依賴注入 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。  本文由 上海-半冷 提供翻譯 什麼是依賴注入 如果你已經知道依賴注入的概念,建構函式和屬性注入

ABP(現代ASP.NET樣板開發框架)系列9ABP設定管理

基於DDD的現代ASP.NET開發框架--ABP系列之9、ABP設定管理 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由山東-李偉提供翻譯 介紹 每個應用程式需要儲存一些設定並在應用程式的某個地方使用這些設定。

ABP(現代ASP.NET樣板開發框架)系列2ABP入門教程

基於DDD的現代ASP.NET開發框架--ABP系列之2、ABP入門教程 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程式的新起點,它旨在成為一個通用的

ABP(現代ASP.NET樣板開發框架)系列17ABP應用——引數有效性驗證

基於DDD的現代ASP.NET開發框架--ABP系列之17、ABP應用層——引數有效性驗證 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 應用程式的輸入資料首先應該被檢驗是否有效。輸入的資料能被使用者或其他應用程式提

ABP(現代ASP.NET樣板開發框架)系列16ABP應用——資料傳輸物件DTOs

基於DDD的現代ASP.NET開發框架--ABP系列之16、ABP應用層——資料傳輸物件(DTOs) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 資料傳輸物件(Data Transfer Objects)用於應用層

ABP(現代ASP.NET樣板開發框架)系列13ABP領域——資料過濾器Data filters

基於DDD的現代ASP.NET開發框架--ABP系列之13、ABP領域層——資料過濾器(Data filters) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 介紹 在資料庫開發中,我們一般會運用軟刪除(soft

ABP(現代ASP.NET樣板開發框架)系列21ABP展現——Javascript函式庫

基於DDD的現代ASP.NET開發框架--ABP系列之21、ABP展現層——Javascript函式庫 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 ASP.NET Boilerplate的js庫提供了一些讓java

ABP(現代ASP.NET樣板開發框架)系列14ABP領域——領域事件Domain events

基於DDD的現代ASP.NET開發框架--ABP系列之14、ABP領域層——領域事件(Domain events) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 在C#中,一個類可以定義其專屬的事件並且其它類可以註冊該事

ABP(現代ASP.NET樣板開發框架)系列5ABP啟動配置

基於DDD的現代ASP.NET開發框架--ABP系列之5、ABP啟動配置 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。  本文由 東莞-天道 提供翻譯 譯者注:在看這一節的內容之前,建議大家先下載module-ze

ABP(現代ASP.NET樣板開發框架)系列3ABP分層架構

基於DDD的現代ASP.NET開發框架--ABP系列之3、ABP分層架構 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 前言 為了減少複雜性和提高程式碼的可重用性,採用分層架構是一種被廣泛接受的技術。為了實現分層的

ABP(現代ASP.NET樣板開發框架)系列19ABP應用——審計日誌

基於DDD的現代ASP.NET開發框架--ABP系列之19、ABP應用層——審計日誌 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 維基百科定義:審計跟蹤(也稱為稽核日誌)是一個安全相關的時間順序記錄,記錄這些記錄的

ABP(現代ASP.NET樣板開發框架)系列15ABP應用——應用服務Application services

基於DDD的現代ASP.NET開發框架--ABP系列之15、ABP應用層——應用服務(Application services) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由東莞-天道提供翻譯 應用服務用於將領

ABP(現代ASP.NET樣板開發框架)系列23ABP展現——異常處理

基於DDD的現代ASP.NET開發框架--ABP系列之23、ABP展現層——異常處理 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 在 web 應用程式中,異常通常是在 MVC Controller actions

ABP(現代ASP.NET樣板開發框架)系列8ABP日誌管理

基於DDD的現代ASP.NET開發框架--ABP系列之8、ABP日誌管理 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 本文由東莞-天道提供翻譯 Server side(伺服器端) ASP.NET Boilerpla

ABP(現代ASP.NET樣板開發框架)系列1ABP總體介紹

基於DDD的現代ASP.NET開發框架--ABP系列之1、ABP總體介紹 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程式的新起點,它旨在成為一個通用的

ABP(現代ASP.NET樣板開發框架)系列12ABP領域——工作單元Unit Of work

基於DDD的現代ASP.NET開發框架--ABP系列之12、ABP領域層——工作單元(Unit Of work) ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 通用連線和事務管理方法 連線和事務管理是使用資料庫的應用程

ABP(現代ASP.NET樣板開發框架)系列22ABP展現——導航欄設定

基於DDD的現代ASP.NET開發框架--ABP系列之22、ABP展現層——導航欄設定 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 每一個WEB應用程式都有導航選單,Abp也為使用者提供了通用的建立和顯示選單方式。