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

Entity Framework 6泛型仓储如何过滤关联实体的软删除状态

可以在泛型仓储中统一过滤Include的软删除实体吗?

当然可以实现!而且有几种实用的方案,能帮你在泛型仓储层统一处理关联实体的软删除过滤,不用在每个具体仓储的查询里重复写检查逻辑。下面我给你详细讲讲:

方案一:EF Core 全局查询筛选器(最推荐)

这是最省心的方案——EF Core提供的全局查询筛选器会自动将软删除过滤逻辑应用到所有查询,包括Include、ThenInclude关联的实体,甚至懒加载的导航属性。

实现步骤:

在你的DbContext的OnModelCreating方法中,给所有继承自BaseEntity的实体添加统一的筛选规则:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // 遍历所有继承自BaseEntity的实体类型
    foreach (var entityType in modelBuilder.Model.GetEntityTypes()
        .Where(t => typeof(BaseEntity).IsAssignableFrom(t.ClrType)))
    {
        // 构建 "实体.IsDeleted == false" 的筛选表达式
        var parameter = Expression.Parameter(entityType.ClrType, "e");
        var filterExpression = Expression.Lambda(
            Expression.Equal(
                Expression.Property(parameter, nameof(BaseEntity.IsDeleted)),
                Expression.Constant(false)),
            parameter);
        
        // 给当前实体添加全局查询筛选器
        modelBuilder.Entity(entityType.ClrType).HasQueryFilter(filterExpression);
    }

    // 其他实体配置逻辑...
}

为什么推荐这个方案?

  • 完全自动化:不管是主实体查询,还是关联实体的Include,都会自动过滤掉IsDeleted = true的记录,你甚至不需要修改现有仓储的Get方法!
  • 避免重复代码:不用在每个仓储、每个查询里手动加软删除过滤条件。
  • 支持所有查询场景:包括懒加载、显式加载、ThenInclude多层关联等。

例外情况处理:

如果偶尔需要查询已删除的实体,只需要在查询时调用IgnoreQueryFilters()即可:

var deletedEntities = dbContext.YourEntities.IgnoreQueryFilters().Where(e => e.IsDeleted).ToList();

方案二:在泛型仓储的Include逻辑中手动过滤(适合无法用全局筛选器的场景)

如果因为某些特殊需求不能使用全局筛选器(比如部分实体不需要软删除过滤),可以在仓储的Get方法中手动处理Include的关联实体过滤。

修改后的仓储Get方法:

public abstract class Repository<T, TOrderBy> where T : BaseEntity 
{ 
    // 假设Set是DbSet<T>的实例,需要在具体仓储中初始化
    protected abstract DbSet<T> Set { get; }

    protected IQueryable<T> Get(Expression<Func<T, bool>> filter = null, params Expression<Func<T, object>>[] includes) { 
        var query = Set.AsQueryable(); 

        // 先过滤主实体的软删除
        query = query.Where(x => !x.IsDeleted);
        
        if (filter != null) { 
            query = query.Where(filter); 
        } 

        if (includes != null) { 
            foreach (var include in includes) { 
                var includeBody = include.Body as MemberExpression;
                if (includeBody == null)
                {
                    query = query.Include(include);
                    continue;
                }

                var navigationType = includeBody.Type;
                // 处理集合类型的导航属性(比如ICollection<U>)
                if (navigationType.IsGenericType && 
                    typeof(ICollection<>).IsAssignableFrom(navigationType.GetGenericTypeDefinition()))
                {
                    var relatedEntityType = navigationType.GetGenericArguments()[0];
                    if (typeof(BaseEntity).IsAssignableFrom(relatedEntityType))
                    {
                        // 构建关联实体的软删除过滤表达式
                        var param = Expression.Parameter(relatedEntityType, "u");
                        var filterExpr = Expression.Lambda(
                            Expression.Equal(
                                Expression.Property(param, nameof(BaseEntity.IsDeleted)),
                                Expression.Constant(false)),
                            param);

                        // 调用EF的Include+Where重载,过滤集合中的已删除实体
                        var includeMethod = typeof(EntityFrameworkQueryableExtensions)
                            .GetMethods()
                            .First(m => m.Name == "Include" && m.GetParameters().Length == 2 &&
                                        m.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
                            .MakeGenericMethod(typeof(T), relatedEntityType);

                        var filteredIncludeExpr = Expression.Lambda(
                            Expression.Call(
                                typeof(Enumerable),
                                "Where",
                                new[] { relatedEntityType },
                                includeBody,
                                filterExpr),
                            include.Parameters);

                        query = (IQueryable<T>)includeMethod.Invoke(null, new object[] { query, filteredIncludeExpr });
                        continue;
                    }
                }
                // 处理引用类型的导航属性(比如一对一关联)
                else if (typeof(BaseEntity).IsAssignableFrom(navigationType))
                {
                    query = query.Include(include);
                    // 添加引用实体的软删除过滤条件
                    var param = Expression.Parameter(typeof(T), "t");
                    var navigationProp = Expression.Property(param, includeBody.Member.Name);
                    var isDeletedProp = Expression.Property(navigationProp, nameof(BaseEntity.IsDeleted));
                    var referenceFilter = Expression.Lambda<Func<T, bool>>(
                        Expression.Equal(isDeletedProp, Expression.Constant(false)),
                        param);
                    query = query.Where(referenceFilter);
                    continue;
                }

                // 非BaseEntity的关联实体,直接普通Include
                query = query.Include(include);
            } 
        } 

        return query; 
    } 
} 

注意事项:

  • 这个方案需要处理两种导航属性:集合类型(一对多)和引用类型(一对一),逻辑相对复杂。
  • 对于多层ThenInclude的场景,这个代码还需要扩展,处理起来会更繁琐。
  • 相比全局筛选器,这种方式更容易出现遗漏,比如忘记处理某些导航类型。

总结

如果你的项目基于EF Core,强烈推荐使用全局查询筛选器,它是最简单、最可靠的统一软删除过滤方案。只有当你有特殊业务需求,无法全局应用筛选器时,再考虑在泛型仓储中手动处理Include的过滤逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 15:07:45