天天看點

ABP架構 - 倉儲

文檔目錄

本節内容:

  • 預設倉儲
  • 自定義倉儲
    • 自定義倉儲接口
    • 自定義倉儲實作
  • 基倉儲方法
    • 查詢
      • 擷取單個實體
      • 擷取實體清單
    • 關于 IQueryable
      • 自定義傳回值
    • 插入
    • 更新
    • 删除
    • 其它
    • 關于異步方法
  • 管理資料庫連接配接
  • 一個倉儲的生命周期
  • 倉儲最佳實踐

領域和映射層之間的媒介使用一種類似集合的接口來通路實體。通常地,每個實體(或聚合根)使用一個分離的倉儲。

在ABP裡,一個倉儲類實作IRepository<TEntity,TPrimaryKey>接口。ABP預設地為每個實體類型自動建立一個預設倉儲。你可以直接注入IRepository<TEntity>(或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<Person>并使用Insert方法。

隻有當實體需要建立一個自定義的倉儲方法時,才需要你建立一個倉儲類。

如下示例,為一個Person實體定義一個倉儲:

public interface IPersonRepository : IRepository<Person>
{

}      

IPersonRepository擴充了IRepository<TEntity>,它用來定義具有int(Int32)類型Id屬性的實體。如果你的實體鍵不是int,你可以擴充IRepository<TEntity,TPrimaryKey>接口,如下所示:

public interface IPersonRepository : IRepository<Person, long>
{

}      

ABP設計成與ORM(對象/關系映射)架構分離、或其它通路資料庫技術分離。倉儲開箱即用地實作了NHibernate和EntityFramework。檢視ABP對這些架構的實作的相關文檔:

  • EntityFramework 內建
  • NHibernate 內建

每個倉儲包含一些通用的來自IRepository<TEntity>接口的方法,我們在此把它們的大部分方法,研究一下。

擷取一個單獨實體

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)的實體。如果無法從資料庫中找到給定Id的實體,将抛出異常。Single方法類似于Get方法,但接受一個表達式,而不是一個Id,是以你可以寫一個lambda表達式來擷取一個實體,用法示例:

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

注意:Single會在無法擷取符合表達式的實體,或是有多個符合表達式的實體時,抛出異常。

FirstOrDefault類似,但在找不到給定Id的實體時,傳回null(代替抛出異常)。如果找到多個實體,則傳回第一個。

Load不從資料庫擷取實體,但為延遲加載建立一個代理對象。如果你隻是使用Id屬性,那麼實質上,不會從資料庫中擷取實體,隻有當你通路實體的其它屬性時,它才從資料庫中擷取實體。出于性能考慮,這個方法用來代替Get。它已經在NHibernate中實作了。如果ORM供應器沒有實作它,Load方法就跟Get方法是一樣的。

擷取一個實體清單

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<T>,是以你在這個方法後可以添加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裡,甚至它可以用在一個json表達式裡。

關于 IQueryable<T>

當你在一個倉儲方法之外調用GetAll(),必須有一個打開的資料庫連接配接,這是因為IQueryable<T>是延遲執行的。它不會執行資料庫的查詢,除非你調用ToList()方法或在一個foreach循環(或其它方式通路查詢裡的項)。是以,當你調用ToList()方法時,資料庫連接配接必須可用。對于一個Web項目,在部分情況你不必關心這個,因為Mvc控制器方法預設都是工作單元,且資料庫連接配接在整個請求裡都是可用的。為更好地了解它,請檢視工作單元文檔。

還有一個另外的方法提供更強大的IQueryable,可以用在工作單元之外。

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

Query方法接受一個lambda表達式(或方法),該表達式(或方法)接收IQueryable<T>并傳回任何類型的對象。例如:

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

由于給定的lambda(或方法)在倉儲方法在執行,當資料庫連接配接可用時,它被執行。你可以在執行查詢後,傳回實體清單、單個實體、一個投射或其它。

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是自增時非常有用。InsertOrUpdate根據Id值執行插入或更新操作。最後,InsertOrUpdateAndGetId在插入或更新實體後,傳回它的Id值。

IRepository定義了更新一個已存在于資料庫的實體的方法,它擷取一個需要更新的實體,傳回相同的實體。

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

大部分時間,你不需要顯式地調用Update方法,因為工作單元會在完成時調用Update方法。見工作單元文檔擷取更多資訊。

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。最後一個根據給定條件删除所有符合的實體,注意:所有比對謂詞的實體可能會從資料庫中先擷取到記憶體,然後再删除(看倉儲如何實作了),是以使用它要小心了,這在有大量符合條件的實體時,可能引起性能問題。

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

ABP支援異常程式設計模式,是以倉儲方法有異步版本。如下例子為一個應用服務方法使用異常模式:

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架構都支援異步。EntityFramework支援。如果不支援,異步方法以同步的方式工作。同樣的,例如,在EntityFramework中InsertAsync和Insert工作方式相同,因為EF直到工作單元完成前(也就是DbContext.SaveChanges),代碼不寫入新的實體。

在一個倉儲方法裡,它不打開或關閉資料庫連接配接,ABP自動管理資料庫連接配接。

當進入一個倉儲方法,ABP自動打開一個資料庫連接配接并開始一個事務,當這個方法結束并傳回時,所有的變化被儲存,事務送出後關閉資料庫連接配接。如果你的倉儲方法抛出任何類型的異常,自動復原事務并關閉資料庫連接配接。這适用于所有實作IRepository接口的類的公開方法。

如果一個倉儲方法調用另一個倉儲方法(即使是一個不同倉儲的方法),它們共享相同的連接配接和事務,第一個方法管理資料庫的連接配接(打開/關閉)。擷取更多的資料庫連接配接管理資訊,請查閱工作單元文檔。

所有倉儲執行個體都是短暫的,它的意思是:它們為每次的使用都進行執行個體化。查閱依賴注入文檔擷取更多資訊。

  • 為一個T類型的實體,盡可能地使用IRepository<T>。不要建立自定義的倉儲,除非确實需要。預定義的倉儲方法可滿足大部分情況。
  • 如果你正在建立一個自定義倉儲(通過擴充IRepository〈TEntity>):
    • 倉儲類應該沒有狀态,也就是說:你不應該定義一個倉儲級别狀态的對象且一個倉儲方法的調用不應該影響另一個調用。
    • 自定義倉儲方法不應該包含業務邏輯或應用邏輯。它應該隻是執行資料相關或ORM相關的任務。
    • 雖然倉儲可以使用依賴注入,但盡可能少或不定義對其它服務的依賴。

kid1412附:英文原文:http://www.aspnetboilerplate.com/Pages/Documents/Repositories

kid1412聲明:轉載請把此段聲明完整地置于文章頁面明顯處,并保留個人在部落格園的連結:http://www.cnblogs.com/kid1412/(可點選跳轉)。

繼續閱讀