1. 程式人生 > >ABP官方文檔翻譯 3.3 倉儲

ABP官方文檔翻譯 3.3 倉儲

upd 集合接口 工作單元 where tab erp let 不依賴 實例

倉儲

  • 默認倉儲
  • 自定義倉儲
    • 自定義倉儲接口
    • 自定義倉儲實現
  • 基礎倉儲方法管理數據庫連接
    • 查詢
      • 獲取單個實體
      • 獲取實體列表
    • 關於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 倉儲