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