You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
abp/docs/zh-Hans/Repositories.md

122 lines
5.2 KiB

# 仓储
"*在领域层和数据映射层之间进行中介,使用类似集合的接口来操作领域对象.*" (Martin Fowler).
实际上,仓储用于领域对象在数据库(参阅[实体](Entities.md))中的操作, 通常每个 **聚合根** 或不同的实体创建对应的仓储.
## 通用(泛型)仓储
ABP为每个聚合根或实体提供了 **默认的通用(泛型)仓储** . 你可以在服务中[注入](Dependency-Injection.md) `IRepository<TEntity, TKey>` 使用标准的**CRUD**操作. 用法示例:
````C#
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集合.
> 上面的示例在[实体](Entities.md)与[DTO](Data-Transfer-Objects.md)之间使用了手动映射. 参阅 [对象映射](Object-To-Object-Mapping.md) 了解自动映射的使用方式.
通用仓储提供了一些开箱即用的标准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类. 但是建议继承现有的仓储接口和类, 获得开箱即用的标准方法使你的工作更轻松.
#### 自定义仓储接口
首先在领域层定义一个仓储接口:
```c#
public interface IPersonRepository : IRepository<Person, Guid>
{
Task<Person> FindByNameAsync(string name);
}
```
此接口扩展了 `IRepository<Person, Guid>` 以使用已有的通用仓储功能.
#### 自定义仓储实现
自定义存储库依赖于你使用的数据访问工具. 在此示例中, 我们将使用Entity Framework Core:
````C#
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 集成文档](Entity-Framework-Core.md).