You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Entity Framework Core创建过滤软删除嵌套集合的通用扩展方法

实现 IncludeNotDeleted 扩展方法

核心思路

借助EF Core的表达式树处理能力,在扩展方法中自动为指定导航属性添加Where(e => !e.IsDeleted)过滤逻辑,完全复用原生Include的查询翻译机制,确保过滤操作在数据库层面执行,避免额外内存开销。

代码实现

using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

public static class QueryableExtensions
{
    // 处理一对多集合导航属性
    public static IQueryable<T> IncludeNotDeleted<T, TProperty>(this IQueryable<T> source, 
        Expression<Func<T, IEnumerable<TProperty>>> navigationPropertyPath)
        where T : class
        where TProperty : BaseEntity
    {
        // 构建 !e.IsDeleted 的过滤表达式
        var param = Expression.Parameter(typeof(TProperty), "e");
        var isDeletedProp = Expression.Property(param, nameof(BaseEntity.IsDeleted));
        var notDeletedExpr = Expression.Not(isDeletedProp);
        var filterLambda = Expression.Lambda<Func<TProperty, bool>>(notDeletedExpr, param);

        // 组合导航属性与过滤条件,模拟 Include(p => p.Comments.Where(...)) 的结构
        var whereCall = Expression.Call(
            typeof(Enumerable), 
            nameof(Enumerable.Where), 
            new[] { typeof(TProperty) },
            navigationPropertyPath.Body, 
            filterLambda);

        var newNavExpr = Expression.Lambda<Func<T, IEnumerable<TProperty>>>(
            whereCall, 
            navigationPropertyPath.Parameters);

        return source.Include(newNavExpr);
    }

    // 可选:处理一对一单个实体导航属性
    public static IQueryable<T> IncludeNotDeleted<T, TProperty>(this IQueryable<T> source,
        Expression<Func<T, TProperty>> navigationPropertyPath)
        where T : class
        where TProperty : BaseEntity
    {
        var navAccess = navigationPropertyPath.Body;
        var isDeletedProp = Expression.Property(navAccess, nameof(BaseEntity.IsDeleted));
        var notDeletedExpr = Expression.Not(isDeletedProp);
        var nullCheck = Expression.NotEqual(navAccess, Expression.Constant(null, typeof(TProperty)));
        var combinedCondition = Expression.AndAlso(nullCheck, notDeletedExpr);

        // 仅保留未删除的关联实体,否则返回null
        var conditionalExpr = Expression.Condition(
            combinedCondition,
            navAccess,
            Expression.Constant(null, typeof(TProperty)));

        var newNavExpr = Expression.Lambda<Func<T, TProperty>>(
            conditionalExpr,
            navigationPropertyPath.Parameters);

        return source.Include(newNavExpr);
    }
}

使用示例

  • 一对多集合场景:
    _dbContext.Posts.IncludeNotDeleted(p => p.Comments)
    
    等效于手动写Include(p => p.Comments.Where(c => !c.IsDeleted)),生成的SQL完全一致。
  • 一对一实体场景:
    _dbContext.Posts.IncludeNotDeleted(p => p.Author)
    
    自动过滤已删除的关联作者,仅返回未删除的实体或null。

注意事项

  • 所有关联实体必须继承自包含IsDeleted属性的BaseEntity,且该属性已正确映射到数据库字段。
  • 该方法完全依赖EF Core原生查询翻译,不会产生额外数据库请求或内存中数据过滤操作,性能与手动编写过滤逻辑一致。

内容的提问来源于stack exchange,提问作者Grigoryants Artem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 16:02:13