如何为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
相关产品推荐
相关产品推荐

