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

點這裏進入ABP系列文章總目錄html

 

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

 

ABP是「ASP.NET Boilerplate Project (ASP.NET樣板項目)」的簡稱。github

ABP的官方網站http://www.aspnetboilerplate.com數據庫

ABP在Github上的開源項目https://github.com/aspnetboilerplate架構

 


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

倉儲定義:「在領域層和數據映射層的中介,使用相似集合的接口來存取領域對象」(Martin Fowler)。框架

實際上,倉儲被用於領域對象在數據庫上的操做(實體Entity和值對象Value types)。通常來講,咱們針對不一樣的實體(或聚合根Aggregate Root)會建立相對應的倉儲。異步

IRepository接口 

在ABP中,倉儲類要實現IRepository接口。最好的方式是針對不一樣倉儲對象定義各自不一樣的接口。async

針對Person實體的倉儲接口聲明的示例以下所示:ide

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系列文章總目錄

相關文章
相關標籤/搜索