源码位置
仓储实现的核心在:
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.csframework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
接口树的设计
csharp
IRepository // 基接口:IsChangeTrackingEnabled, ProviderName
└── IReadOnlyBasicRepository<T> // 只读:GetList, GetCount, GetPagedList
└── IBasicRepository<T> // 写入:Insert, Update, Delete
└── IReadOnlyRepository<T> // 高级查询:WithDetails, GetQueryable
└── IRepository<T> // 复合接口:FindAsync(predicate), DeleteAsync(predicate)
IRepository<TEntity, TKey> 是 IRepository<TEntity> + IReadOnlyBasicRepository<TEntity, TKey> + IBasicRepository<TEntity, TKey> 的复合。日常使用直接注入这一个接口就够了。
自动过滤:RepositoryBase 的核心
RepositoryBase.ApplyDataFilters 中,自动为所有查询添加了两个重要的 WHERE 条件:
csharp
protected virtual TQueryable ApplyDataFilters<TQueryable, TOtherEntity>(TQueryable query)
where TQueryable : IQueryable<TOtherEntity>
{
// 自动过滤软删除
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TOtherEntity)))
{
query = (TQueryable)query.WhereIf(
DataFilter.IsEnabled<ISoftDelete>(),
e => ((ISoftDelete)e!).IsDeleted == false
);
}
// 自动过滤多租户
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TOtherEntity)))
{
var tenantId = CurrentTenant.Id;
query = (TQueryable)query.WhereIf(
DataFilter.IsEnabled<IMultiTenant>(),
e => ((IMultiTenant)e!).TenantId == tenantId
);
}
return query;
}
这段代码是仓储模式价值的集中体现:业务代码不需要写 WHERE IsDeleted = false 和 WHERE TenantId = @id,仓储自动处理。
DeleteDirectAsync 和 DeleteAsync 的区别
csharp
// DeleteAsync ------ 先查询后删除,会触发审计、软删除等
public async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, ...)
{
var entities = await GetListAsync(predicate);
foreach (var entity in entities) await DeleteAsync(entity);
}
// DeleteDirectAsync ------ 直接 SQL 删除,跳过所有过滤器
public abstract Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate, ...);
DeleteDirectAsync 直接执行 DELETE SQL,不会触发软删除、审计日志和多租户过滤。适合批量清理过期数据,但用的时候要清楚它的副作用。
FindAsync vs GetAsync 的语义差异
csharp
// RepositoryBase.cs
public async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate, ...)
{
// 可能返回 null
}
public async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate, ...)
{
var entity = await FindAsync(predicate);
if (entity == null) throw new EntityNotFoundException<TEntity>(); // 找不到就抛异常
return entity;
}
GetAsync 是 FindAsync + 抛异常的封装。业务上确定数据一定存在时用 GetAsync,可能不存在时用 FindAsync。
EF Core 仓储的实现
csharp
// EfCoreRepository.cs
public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>
where TDbContext : IEfCoreDbContext
{
protected virtual Task<TDbContext> GetDbContextAsync()
{
// 非多租户实体始终使用 Host 连接串
if (!EntityHelper.IsMultiTenant<TEntity>())
{
using (CurrentTenant.Change(null))
{
return _dbContextProvider.GetDbContextAsync();
}
}
return _dbContextProvider.GetDbContextAsync();
}
}
这段代码处理了一个重要的情况:非多租户实体(如租户列表本身)始终使用 Host 库的连接串读取,不会因为当前租户切换而读到错误的数据。
实战:自定义仓储的正确做法
csharp
// 1. 定义接口
public interface IBookRepository : IRepository<Book, Guid>
{
Task<List<Book>> SearchByNameAsync(string keyword);
}
// 2. 实现
public class EfCoreBookRepository : EfCoreRepository<MyDbContext, Book, Guid>, IBookRepository
{
public EfCoreBookRepository(IDbContextProvider<MyDbContext> dbContextProvider)
: base(dbContextProvider) { }
public async Task<List<Book>> SearchByNameAsync(string keyword)
{
// 使用 GetQueryableAsync 获取 IQueryable,会自动应用数据过滤
return await (await GetQueryableAsync())
.Where(b => b.Name.Contains(keyword))
.ToListAsync();
}
}
// 3. 注入使用
public class BookAppService : ApplicationService
{
private readonly IBookRepository _bookRepo;
// IBookRepository 替代 IRepository<Book, Guid>,在需要复杂查询时使用
}
对比总结
泛型 IRepository<TEntity, TKey>:简单 CRUD,直接用
自定义仓储接口:复杂查询、需要复用查询逻辑时用