文檔目錄
本節內容:
領域和映射層之間的媒介使用一種類似集合的接口來訪問實體。通常地,每個實體(或聚合根)使用一個分離的倉儲。
默認倉儲
在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對這些框架的實現的相關文檔:
基倉儲方法
每個倉儲包含一些通用的來自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接口的類的公開方法。
如果一個倉儲方法調用另一個倉儲方法(即使是一個不同倉儲的方法),它們共享相同的連接和事務,第一個方法管理數據庫的連接(打開/關閉)。獲取更多的數據庫連接管理信息,請查閱工作單元文檔。
一個倉儲的生命周期
所有倉儲實例都是短暫的,它的意思是:它們為每次的使用都進行實例化。查閱依賴注入文檔獲取更多信息。
倉儲最佳實踐