基於DDD的.NET開發框架 - ABP倉儲實現

返回ABP系列html

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

ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程序的新起點,它旨在成爲一個通用的WEB應用程序框架和項目模板。github

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

ABP官方文檔:http://www.aspnetboilerplate.com/Pages/Documents編程

Github上的開源項目:https://github.com/aspnetboilerplateapp

1、基本操做

倉儲的定義:位於領域層和數據映射層之間,使用相似集合的接口來訪問領域對象。框架

在實踐中,倉儲是執行領域對象(實體和值對象)的數據庫操做。通常地,一個分離的倉儲用於一個實體(或者聚合根)。異步

一、IRepository接口async

在ABP中,一個倉儲類應該實現一個IRepository接口。爲每個倉儲定義一個接口是一個好的作法。異步編程

一個Person實體的倉儲定義以下:

        public interface IPersonRepository : IRepository<Person>
        {
        }

IPersonRepository擴展了IRepository,它用於定義擁有主鍵類型爲int32的實體。若是你的實體不是int,那麼能夠擴展IRepository<tentity,tprimarykey>接口,以下所示:

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

IRepository爲倉儲類定義了最通用的方法,如select,insert,update和delete方法(CRUD操做)。大多數狀況下,這些方法對於簡單的實體是足夠了。若是這些方法對於一個實體來講已經足夠了,那麼就沒有必要爲這個實體建立倉儲接口和倉儲類了。

 

1)、查詢:

IRepository定義了通用的方法,從數據庫中檢索實體。

獲取單個實體:

TEntity Get(TPrimaryKey id);
Task<TEntity> GetAsync(TPrimaryKey id);
TEntity Single(Expression<Func<TEntity, bool>> predicate);
Task<TEntity> SingleAsync(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 => p.Name == "hi joye.net");

注意:若是根據給定的條件沒有查找出實體或者查出不止一個實體,那麼Single方法會拋出異常。

FirstOrDefault是類似的,可是若是根據給的的Id或者表達式沒有找到實體,那麼就會返回null。若是對於給定的條件存在不止一個實體,那麼會返回找到的第一個實體。

Load方法不會從數據庫中檢索實體,可是會建立一個用於懶加載的代理對象。若是你只用了Id屬性,那麼Entity實際上並無檢索到。只有你訪問實體的其餘屬性,纔會從數據庫中檢索。考慮到性能因素,這個就能夠替換Get方法。這在NHiberbate中也實現了。若是ORM提供者沒有實現它,那麼Load方法會和Get方法同樣地工做。

一些方法有用於async編程模型的異步版本。

得到實體的列表:

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 = _personRepository.GetAllList();
var somePeople = _personRepository.GetAllList(person => person.IsActive && person.Age > 42);

GetAll返回的類型是IQueryable。所以,你能夠在此方法以後添加Linq方法。用法以下:

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

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

有了GetAll方法,幾乎全部的查詢均可以使用Linq重寫。甚至能夠用在一個鏈接表達式中。

關於IQueryable:脫離了倉儲方法調用GetAll()方法時,數據庫鏈接必需要打開。這是由於IQueryable的延遲執行。直到調用ToList()方法或者在foreach循環中使用IQueryable(或者訪問查詢到的元素)時,纔會執行數據庫查詢操做。所以,當調用ToList()方法時。數據庫鏈接必須打開。這能夠經過ABP中的UnitOfWork特性標記調用者方法來實現。注意:應用服務方法默認已是UnitOfWork,所以,即便沒有爲應用服務層方法添加UnitOfWork特性,GetAll()方法也會正常工做。

這些方法也存在用於異步編程模型的asyn版本。

自定義返回值:

也存在提供了IQueryable的額外方法,在調用的方法中不須要使用UnitOfWork。

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

Query方法接受一個接收IQueryable的lambda(或方法),並返回任何對象的類型。用法以下:

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

在該倉儲方法中,由於執行了給定的lambda(或方法),它是在數據庫鏈接打開的時候執行的。你能夠返回實體列表,單個實體,一個投影或者執行了該查詢的其餘東西。

 

2)、插入:

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

Insert方法簡化了將一個實體插入數據庫,並將剛剛插入的實體返回。InsertAndGetId方法返回了新插入實體的Id。若是實體的Id是自動增加的而且須要最新插入實體的Id,那麼該方法頗有用。InsertOrUpdate方法經過檢查Id的值插入或更新給定的實體。最後,當插入或者更新以後,InsertOrUpdateAndGetId返回該實體的值。

全部的方法都存在用於異步編程模型的async版本。

 

3)、更新:

IRepository定義了一個方法來更新數據庫中已存在的實體。它能夠得到要更新的實體並返回相同的實體對象。

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

 

4)、刪除:

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。

最後一個方法接受一個刪除符合給定條件的全部實體的方法。注意,匹配給定謂詞的全部實體都會從數據庫中檢索到而後被刪除。所以,當心使用它,若是給定的條件存在太多的實體,那麼可能會形成性能問題。

 

5)、其餘:

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<Func<TEntity, bool>> predicate);

 

6)、關於異步方法:

ABP支持異步編程模型(APM)。所以,倉儲方法有異步版本。下面是一個使用了異步模型的應用服務方法例子:

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方法是異步的,並使用了具備await關鍵字的GetAllListAsync方法。

也許不是全部的ORM框架都支持Async,可是EntityFramework支持。若是不支持,異步倉儲方法就會同步進行。好比,在EF中,InsertAsync和Insert是等效的,由於直到工做單元完成(Dbcontext.SaveChanges),EF纔會將新的實體寫入數據庫。

 

二、倉儲實現

ABP的設計獨立於一個特定的ORM(對象/關係映射)框架或者訪問數據庫的其餘技術。經過實現倉儲接口,可使用任何框架。

ABP使用NHibernate和 EntityFramework實現了開箱即用的倉儲。關於這兩個ORM框架能夠關注後面的文檔。

當使用NHibernate或EntityFramework時,若是標準方法是足夠使用的話,那麼沒必要爲實體類建立倉儲了。你能夠直接注入IRepository(或IRepository<tentity,tprimarykey>)。下面是使用了一個倉儲將一個實體插入數據庫的應用服務例子:

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,並使用了Insert方法。這樣,當你須要爲一個實體建立一個自定義倉儲方法時,你才應該爲該實體建立一個倉儲類。

 

三、管理數據庫的鏈接:

在倉儲方法中,數據庫鏈接是沒有打開的或是關閉的。ABP對於數據庫鏈接的管理是自動處理的。

當將要進入一個倉儲方法時,數據庫鏈接會自動打開,而且事務自動開始。當倉儲方法結束並返回的時候,ABP會自動完成:保存全部的更改,完成事務的提交和關閉數據庫鏈接。若是倉儲方法拋出任何類型的異常,那麼事務會自動回滾並關閉數據庫。這對於全部的實現了IRepository接口的類的公共方法都是成立的。

若是一個倉儲方法調用了其餘的倉儲方法,那麼它們會共享相同的鏈接和事務。進入倉儲的第一個方法會管理數據庫的鏈接。更多信息,請留意後面博客的工做單元。

 

四、倉儲的生命週期

全部的倉儲實例都是Transient(每次使用時都會實例化)的。ABP強烈推薦使用依賴注入技術。當一個倉儲類須要注入時,依賴注入的容器會自動建立該類的新實例。

 

五、倉儲的最佳實踐

一、對於一個T類型的實體,使用IRepository倉儲接口。除非真的須要,不然不要建立自定義的倉儲。預約義的倉儲方法對於不少狀況足夠用了。

二、若是你正在建立一個自定義的倉儲(經過擴展IRepository):

1)、倉儲類應該是無狀態的。這意味着,你不該該定義倉儲級別的狀態對象,並且一個倉儲方法調用不該該影響其餘的調用。

2)、自定義倉儲方法不該該包含業務邏輯或者應用邏輯,而應該只執行數據相關的或者orm特定的任務。

3)、當倉儲使用依賴注入時,給其餘服務定義更少的或者不要定義依賴。