倉儲
"在領域層和資料對映層之間進行中介,使用類似集合的介面來操作領域物件." (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集合.瞭解如何使用 非同步擴充套件方法, 如 ToListAsync() (建議始終使用非同步) 而不是 ToList()上面的示例在實體與DTO之間使用了手動對映. 可以瞭解自動對映的使用方式.通用倉儲提供了一些開箱即用的標準CRUD功能:
提供 Insert 方法用於儲存新實體.提供 Update 和 Delete 方法透過實體或實體id更新或刪除實體.提供 Delete 方法使用條件表示式過濾刪除多個實體.實現了 IQueryable<TEntity>, 所以你可以使用LINQ和擴充套件方法 FirstOrDefault, Where, OrderBy, ToList 等...所有方法都具有 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(); }}
IQueryable & 非同步操作IRepository 繼承自 IQueryable,這意味著你可以直接使用LINQ擴充套件方法.
但在你使用標準的應用程式啟動模板時會發現無法在應用層或領域層使用這些非同步擴充套件方法,因為:
這裡非同步方法不是標準LINQ方法,它們定義在Microsoft.EntityFrameworkCoreNuget包中.標準模板應用層與領域層不引用EF Core 包, 以實現資料庫提供程式獨立.強烈建議使用非同步方法! 在執行資料庫查詢時不要使用同步LINQ方法,以便能夠開發可伸縮的應用程式.
根據你的需求和開發模式,你可以根據以下選項使用非同步方法:
1: 引用EF Core
最簡單的方法是在你想要使用非同步方法的專案直接引用EF Core包.
新增Volo.Abp.EntityFrameworkCore NuGet包到你的專案間接引用EF Core包. 這可以確保你的應用程式其餘部分相容正確版本的EF Core.
如果使用的是MongoDB,則需要將[Volo.Abp.MongoDB] NuGet包新增到專案中. 但在這種情況下你也不能直接使用非同步LINQ擴充套件(例如ToListAsync),因為MongoDB不提供 IQueryable<T>的非同步擴充套件方法,而是提供 IMongoQueryable<T>. 你需要先將查詢強制轉換為 IMongoQueryable<T> 才能使用非同步擴充套件方法.
2: 自定義倉儲方法
你始終可以建立自定義倉儲方法並使用特定資料庫提供程式的API,比如這裡的非同步擴充套件方法.
此方法建議;
如果你想完全隔離你的領域和應用層和資料庫提供程式.如果你開發可重用的應用模組,並且不想強制使用特定的資料庫提供程式,這應該作為一種最佳實踐.3: IAsyncQueryableExecuter
IAsyncQueryableExecuter 是一個用於非同步執行 IQueryable<T> 物件的服務,不依賴於實際的資料庫提供程式.
示例: 注入並使用 IAsyncQueryableExecuter.ToListAsync() 方法
using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using Volo.Abp.Application.Dtos;using Volo.Abp.Application.Services;using Volo.Abp.Domain.Repositories;using Volo.Abp.Linq;namespace AbpDemo{ public class ProductAppService : ApplicationService, IProductAppService { private readonly IRepository<Product, Guid> _productRepository; private readonly IAsyncQueryableExecuter _asyncExecuter; public ProductAppService( IRepository<Product, Guid> productRepository, IAsyncQueryableExecuter asyncExecuter) { _productRepository = productRepository; _asyncExecuter = asyncExecuter; } public async Task<ListResultDto<ProductDto>> GetListAsync(string name) { //Create the query var query = _productRepository .Where(p => p.Name.Contains(name)) .OrderBy(p => p.Name); //Run the query asynchronously List<Product> products = await _asyncExecuter.ToListAsync(query); //... } }}
ApplicationService 和 DomainService 基類已經預屬性注入了 AsyncExecuter 屬性,所以你可直接使用.
ABP框架使用實際資料庫提供程式的API非同步執行查詢.雖然這不是執行查詢的常見方式,但它是使用非同步API而不依賴於資料庫提供者的最佳方式.
值物件TODO領域服務TODO規約TODO