惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
Google DeepMind News
Google DeepMind News
腾讯CDC
V
Visual Studio Blog
Engineering at Meta
Engineering at Meta
博客园 - 司徒正美
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tailwind CSS Blog
Vercel News
Vercel News
爱范儿
爱范儿
Last Week in AI
Last Week in AI
G
Google Developers Blog
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
有赞技术团队
有赞技术团队
D
DataBreaches.Net
博客园_首页
J
Java Code Geeks
云风的 BLOG
云风的 BLOG
V
V2EX
A
About on SuperTechFans
H
Hackread – Cybersecurity News, Data Breaches, AI and More
人人都是产品经理
人人都是产品经理

博客园 - 邓磊Lei

我可以双击 shift 打开 visual studio 的代码搜索 我决定把 gittoy 更名为 git blame for vs 为了可以重新拥抱 Visual Studio, 我做了一个 vs 版的 gittoolbox ABP DTO 与对象映射:AutoMapper Profile 配置与 Mapperly 源生成器 ABP 应用服务源码解读:ApplicationService 基类内置了哪些能力 ABP DDD 实体源码解读:7 层基类的设计逻辑和适用场景 ABP 模块系统源码学习:启动时模块是如何加载和排序的 为什么 Go 一个 HTTP 服务可以同时处理数万连接 gin怎么处理一次http请求的 我的经验:git提交信息分别用什么icon C# 也能像 Python 一样写脚本 | .NET 10 构建基于文件的应用 .NET 10 使用 Microsoft.AspNetCore.OpenApi 实现 API 版本管理 ASP.NET Core 内存缓存实战:一篇搞懂该怎么配、怎么避坑 Microsoft Agent Framework + Kimi API 实战:控制台应用跑通单次与多轮 Agent 对话 开发实战:asp.net core + ef core 实现动态可扩展的分页方案 聊聊 ASP.NET Core 中间件和过滤器的区别 Python 入门:从“其他语言”到 Pythonic 思维的完整迁移手册 .NET 进阶之路:异步、并发与内存管理的系统性认知 Redis:延迟双删的适用边界与落地细节 Serilog:从结构化日志认知到 .NET 工程落地 EF Core 原生 SQL 实战:FromSql、SqlQuery 与对象映射边界 EF Core 拦截器实战:SaveChangesInterceptor、CommandInterceptor 与审计落地 ASP.NET Core 外部依赖调用治理实战:HttpClientFactory、Polly 与幂等边界 .NET .Result 避坑指南:不同框架下的死锁与线程池饥饿 EF Core 慢查询排查实战:TagWith、OpenTelemetry、执行计划,30 分钟定位性能瓶颈 EF Core 并发冲突实战:乐观锁、RowVersion 与 DbUpdateConcurrencyException 怎么处理 EF Core 写入链路深拆:从 ChangeTracker 到 SQL Batch 的性能诊断与优化 ASP.NET Core 认证鉴权实战:JWT、Policy 与权限边界怎么落地 C# 异步编程深水区:Task、ValueTask、线程池饥饿与背压设计 EF Core 查询性能黑洞:Include、投影与跟踪策略的边界
ABP 仓储模式源码解读:自动过滤是如何实现的
DLForge · 2026-08-04 · via 博客园 - 邓磊Lei

源码位置

仓储实现的核心在:

  • framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs
  • framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs

接口树的设计

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 条件:

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 的区别

// 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 的语义差异

// 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 仓储的实现

// 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 库的连接串读取,不会因为当前租户切换而读到错误的数据。

实战:自定义仓储的正确做法

// 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,直接用
自定义仓储接口:复杂查询、需要复用查询逻辑时用