深入淺出-可定製倉儲設計

"在領域層和數據映射層之間進行中介,使用類似集合的接口來操作領域對象." (Martin Fowler)。

實際上,倉儲用於領域對象在數據庫(參閱實體)中的操作,通常每個 聚合根 或不同的實體創建對應的倉儲。

通用(泛型)倉儲

ABP爲每個聚合根或實體提供了 默認的通用(泛型)倉儲 . 你可以在服務中注入 IRepository<TEntity, TKey> 使用標準的CRUD操作. 用法示例:

public class PersonAppService : ApplicationService
{
    private readonly IRepository<Person, Guid> _personRepository;

    public PersonAppService(IRepository<Person, Guid> personRepository)
    {
        _personRepository = personRepository;
    }

    public async Task Create(CreatePersonDto input)
    {
        var person = new Person { Name = input.Name, Age = input.Age };

        await _personRepository.InsertAsync(person);
    }

    public List<PersonDto> GetList(string nameFilter)
    {
        var people = _personRepository
            .Where(p => p.Name.Contains(nameFilter))
            .ToList();

        return people
            .Select(p => new PersonDto {Id = p.Id, Name = p.Name, Age = p.Age})
            .ToList();
    }
}

在這個例子中:

  • PersonAppService 在它的構造函數中注入了 IRepository<Person, Guid>。
  • Create 方法使用了 InsertAsync 創建並保存新的實體。
  • GetList 方法使用標準LINQ Where 和 ToList 方法在數據源中過濾並獲取People集合。

上面的示例在實體DTO之間使用了手動映射. 參閱 對象映射 瞭解自動映射的使用方式.

通用倉儲提供了一些開箱即用的標準 CRUD 功能:

  • 提供 Insert 方法用於保存新實體。
  • 提供 Update 和 Delete 方法通過實體或實體id更新或刪除實體。
  • 提供 Delete 方法使用條件表達式過濾刪除多個實體。
  • 實現了 IQueryable<TEntity>, 所以你可以使用LINQ和擴展方法 FirstOrDefaultWhereOrderByToList 等...
  • 所有方法都具有 sync(同步) 和 async(異步) 版本。

基礎倉儲

IRepository<TEntity, TKey> 接口擴展了標準 IQueryable<TEntity> 你可以使用標準LINQ方法自由查詢.但是,某些ORM提供程序或數據庫系統可能不支持IQueryable接口。

ABP提供了 IBasicRepository<TEntity, TPrimaryKey> 和 IBasicRepository<TEntity> 接口來支持這樣的場景. 你可以擴展這些接口(並可選擇性地從BasicRepositoryBase派生)爲你的實體創建自定義存儲庫。

依賴於 IBasicRepository 而不是依賴 IRepository 有一個優點, 即使它們不支持 IQueryable 也可以使用所有的數據源, 但主要的供應商, 像 Entity Framework, NHibernate 或 MongoDb 已經支持了 IQueryable。

因此, 使用 IRepository 是典型應用程序的 建議方法. 但是可重用的模塊開發人員可能會考慮使用 IBasicRepository 來支持廣泛的數據源。

只讀倉儲

對於想要使用只讀倉儲的開發者,我們提供了IReadOnlyRepository<TEntity, TKey> 與 IReadOnlyBasicRepository<Tentity, TKey>接口。

無主鍵的通用(泛型)倉儲

如果你的實體沒有id主鍵 (例如, 它可能具有複合主鍵) 那麼你不能使用上面定義的 IRepository<TEntity, TKey>, 在這種情況下你可以僅使用實體(類型)注入 IRepository<TEntity>。

IRepository<TEntity> 有一些缺失的方法, 通常與實體的 Id 屬性一起使用。由於實體在這種情況下沒有 Id 屬性, 因此這些方法不可用. 比如 Get 方法通過id獲取具有指定id的實體. 不過, 你仍然可以使用IQueryable<TEntity>的功能通過標準LINQ方法查詢實體。

自定義倉儲

對於大多數情況, 默認通用倉儲就足夠了. 但是, 你可能會需要爲實體創建自定義倉儲類.

自定義倉儲示例

ABP不會強制你實現任何接口或從存儲庫的任何基類繼承. 它可以只是一個簡單的POCO類。但是建議繼承現有的倉儲接口和類,獲得開箱即用的標準方法使你的工作更輕鬆。

自定義倉儲接口

首先在領域層定義一個倉儲接口:

public interface IPersonRepository : IRepository<Person, Guid>
{
    Task<Person> FindByNameAsync(string name);
}

此接口擴展了 IRepository<Person, Guid> 以使用已有的通用倉儲功能。

自定義倉儲實現

自定義存儲庫依賴於你使用的數據訪問工具。在此示例中,我們將使用Entity Framework Core:

public class PersonRepository : EfCoreRepository<MyDbContext, Person, Guid>, IPersonRepository
{
    public PersonRepository(IDbContextProvider<TestAppDbContext> dbContextProvider) 
        : base(dbContextProvider)
    {

    }

    public async Task<Person> FindByNameAsync(string name)
    {
        return await DbContext.Set<Person>()
            .Where(p => p.Name == name)
            .FirstOrDefaultAsync();
    }
}

你可以直接使用數據庫訪問提供程序 (本例中是 DbContext ) 來執行操作。有關基於EF Core的自定義倉儲的更多信息,請參閱EF Core 集成文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章