深入浅出-可定制仓储设计
"在领域层和数据映射层之间进行中介,使用类似集合的接口来操作领域对象." (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
方法使用标准LINQWhere
和ToList
方法在数据源中过滤并获取People集合。
通用仓储提供了一些开箱即用的标准 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();
}
}
你可以直接使用数据库访问提供程序 (本例中是 DbContext
) 来执行操作。有关基于EF Core的自定义仓储的更多信息,请参阅EF Core 集成文档。