ABP官方文檔翻譯 3.3 倉儲
倉儲
- 默認倉儲
- 自定義倉儲
- 自定義倉儲接口
- 自定義倉儲實現
- 基礎倉儲方法管理數據庫連接
- 查詢
- 獲取單個實體
- 獲取實體列表
- 關於IQueryable
- 自定義返回值
- 插入
- 更新
- 刪除
- 其他
- 關於異步方法
- 查詢
- 管理數據庫連接
- 倉儲生命周期
- 倉儲最佳實踐
協調領域和數據映射層,使用類集合接口訪問領域對象。"(Martin Fowler)
實際上,倉儲用來執行領域對象的數據庫操作(實體和值類型)。通常,每個對象(或聚合根)使用單獨的倉儲。
默認倉儲
在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)類型主鍵的實體。如果實體的主鍵不是int類型,可以繼承IRepository<TEntity,TPrimaryKey>接口,如下所示:
public interface IPersonRepository : IRepository<Person, long>
{
}
自定義倉儲實現
ABP設計為獨立於特定的ORM(對象/關系映射)框架或其他訪問數據庫的技術。在NHibernate和EntityFramework實現的倉儲都是開箱即用的。可參見這些框架的相關文檔:
- NHibernate integration
- EntityFramework integration
基礎倉儲方法
每個倉儲類有些來自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。所以,使用Single可以寫一個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編寫。即使它被用在連接表達式。
關於IQueryable
當調用倉儲的GetAll()方法時,必須有一個打開的數據庫連接。這是因為IQueryable<T>是延遲執行的。在調用ToList()方法或在foreach循環(或訪問查詢項時)裏使用IQueryable<T>之前,它不會執行數據庫查詢。所以,當調用ToList()方法時,數據庫連接必須是可用的。對於Web應用,不用關心數據庫連接的問題,因為MVC控制器方法默認為一個工作單元,數據庫連接在整個請求期間都是可用的。參見UnitOfWork文檔了解更多。
自定義返回值
有一個額外的方法,可以使IQueryable在工作單元外使用。
T Query<T>(Func<IQueryable<TEntity>, T> queryMethod);
Query方法接收一個接收IQeryable<T>的lambda表達式(或方法)並返回對象的任何類型。
var people = _personRepository.Query(q => q.Where(p => p.Name.Contains("H")).OrderBy(p => p.Name).ToList());
因為給定的lambda(或方法)在倉儲方法內執行,當數據庫連接可用時執行。可以執行這個查詢返回實體列表、單個實體、一個投射或其他的東西。
插入
IRepository接口定義了insert方法插入實體到數據庫:
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返回插入或更新後實體的Id。
更新
IRepository定義了更新數據庫中已存在實體的方法。它獲取需要更新的實體並返回這個實體對象。
TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity);
大多數時候不需要顯示的調用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是支持的。如果不支持,異步倉儲方法將按同步方式執行。例如,在EF中InsertAsync與Insert方法執行方式相同,因為EF只有直到工作單元完成時才會寫入新實體到數據庫(a.k.a DbContext.SaveChanges)。
管理數據庫連接
數據庫連接不會在倉儲方法中打開或關閉。連接管理由ABP自動管理。
當進入倉儲方法時,數據庫連接自動打開並開始一個事務。當方法結束並返回時,所有的更改被保存,事務提交、關閉數據庫連接,這些由ABP自動完成。如果倉儲方法拋出任何類型的異常,事務自動回滾並關閉數據庫連接。所有實現IRepository接口類的公共方法都是這樣的。
如果一個倉儲方法調用另一個倉儲方法(甚至是不同倉儲的方法),這些方法將共享同樣的連接和事務。數據庫連接由進入倉儲的第一個方法管理(打開或關閉)。關於數據庫連接管理的更多信息,參見工作單元文檔。
倉儲生命周期
所有的倉儲接口都是臨時的。意味著,每次使用都會實例化。參見依賴註入文檔了解更多信息。
倉儲最佳實踐
- 對於泛型T的實體,盡可能使用IRepository<T>接口。除非真的需要不要創建自定義倉儲。預定義的倉儲方法足夠滿足大多數場景。
- 如果創建了一個自定義倉儲(通過擴展IRepository<TEntity>接口實現):
- 倉儲類應該是無狀態的。意味著,不應該定義倉儲級別狀態的對象,並且一個倉儲方法的調用不能影響另一個倉儲方法的調用。
- 自定義倉儲方法不應該包含業務邏輯或應用邏輯。它應該僅僅執行數據相關或orm特定的任務。
- 當倉儲可以使用依賴註入時,盡量少或不依賴於其他服務。
返回主目錄
ABP官方文檔翻譯 3.3 倉儲