ABP 仓储模式源码解读:自动过滤是如何实现的

源码位置

仓储实现的核心在:

  • framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs
  • framework/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 = falseWHERE 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;
}

GetAsyncFindAsync + 抛异常的封装。业务上确定数据一定存在时用 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,直接用
自定义仓储接口:复杂查询、需要复用查询逻辑时用
相关推荐
硅基喵17 小时前
ABP DDD 实体源码解读:7 层基类的设计逻辑和适用场景
dotnet
硅基喵3 天前
ABP 模块系统源码学习:启动时模块是如何加载和排序的
dotnet
硅基喵3 个月前
C# 也能像 Python 一样写脚本 | .NET 10 构建基于文件的应用
dotnet
硅基喵3 个月前
.NET 10 使用 Microsoft.AspNetCore.OpenApi 实现 API 版本管理
dotnet
硅基喵4 个月前
ASP.NET Core 内存缓存实战:一篇搞懂该怎么配、怎么避坑
dotnet
ChaITSimpleLove5 个月前
aiagent-webapi 命令的详细使用说明
dotnet·webapi·ai agent·agent framework·maf·projecttemp
TeamDev5 个月前
使用 Docker 部署 DotNetBrowser 应用程序
运维·ui·docker·容器·桌面应用·dotnet·dotnetbrowser
CSharp精选营5 个月前
.NET命名之谜:它与C#纠缠20年的关系揭秘
c#·.net·dotnet·csharp
VAllen5 个月前
ConcurrentNativeQueue<T>:一个使用 .NET 实现的零 GC 压力的无锁 MPSC 原生队列
c#·.net·性能测试·.net core·dotnet·csharp