如何在EF Core自定义Include方法中为符合条件的集合选择器动态添加Where过滤
如何在EF Core自定义Include方法中为符合条件的集合选择器动态添加Where过滤
我完全理解你的需求:想要做一个通用的Include扩展方法,当操作的主实体和要Include的集合都实现了IObjectWithSecurity接口时,自动给这个集合加上Where(a => a.AllowAccess)的权限过滤,不用每次手动写重复的条件。这个场景在统一权限控制的系统里特别实用。
首先得抓住核心问题:我们需要动态修改传入的propertySelector表达式树——把原来单纯“获取集合”的逻辑(比如i => i.Documents),转换成“获取集合并自动过滤权限”的逻辑(i => i.Documents.Where(a => a.AllowAccess)),再把新的表达式传给EF的Include方法。
解决方案步骤解析
要实现这个目标,我们需要手动构造表达式树,具体分为这几步:
- 先构造固定的权限过滤表达式:
a => a.AllowAccess,这是我们要追加的统一过滤逻辑。 - 从原
propertySelector中提取出“获取集合”的表达式节点(比如i.Documents对应的表达式部分)。 - 构造调用
Queryable.Where方法的表达式,把权限过滤条件绑定到集合上。 - 把这个“集合+过滤”的新表达式作为Lambda的Body,用原参数构造出新的
propertySelector。 - 最后用新的
propertySelector调用EF的Include方法返回结果。
完整实现代码
下面是调整后的完整扩展方法代码,我已经处理了类型转换、表达式树构造的细节,并且补上了缺失的分支逻辑:
using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Microsoft.EntityFrameworkCore; public static class QueryableSecurityExtensions { public static IIncludableQueryable<TRepositoryType, TThisInclude> IncludeCheckingSecurity<TRepositoryType, TThisInclude>( this IQueryable<TRepositoryType> source, Expression<Func<TRepositoryType, TThisInclude>> propertySelector) where TRepositoryType : class { var repoType = typeof(TRepositoryType); var includeType = typeof(TThisInclude); // 校验主实体和要Include的集合是否都符合权限过滤的条件 if (typeof(IObjectWithSecurity).IsAssignableFrom(repoType) && typeof(IEnumerable<IObjectWithSecurity>).IsAssignableFrom(includeType)) { // 1. 构造权限过滤条件:s => s.AllowAccess var securityParam = Expression.Parameter(typeof(IObjectWithSecurity), "s"); var allowAccessProp = Expression.Property(securityParam, nameof(IObjectWithSecurity.AllowAccess)); var filterExpr = Expression.Lambda<Func<IObjectWithSecurity, bool>>(allowAccessProp, securityParam); // 2. 取出原表达式中获取集合的部分(比如 i => i.Documents 里的 i.Documents) var collectionAccess = propertySelector.Body; // 3. 获取Queryable.Where的泛型方法(确保EF能正确翻译成SQL) var whereMethod = typeof(Queryable) .GetMethods(BindingFlags.Public | BindingFlags.Static) .First(m => m.Name == nameof(Queryable.Where) && m.GetParameters().Length == 2) .MakeGenericMethod(typeof(IObjectWithSecurity)); // 4. 构造调用Where后的集合表达式,并转换为目标类型避免类型不匹配 var filteredCollection = Expression.Convert( Expression.Call(whereMethod, collectionAccess, filterExpr), includeType); // 5. 构造新的Lambda表达式,替换原有的propertySelector var newPropertySelector = Expression.Lambda<Func<TRepositoryType, TThisInclude>>( filteredCollection, propertySelector.Parameters); // 6. 用新的表达式调用Include返回结果 return source.Include(newPropertySelector); } else { // 不满足权限过滤条件时,直接返回普通的Include结果 return source.Include(propertySelector); } } } // 你的权限接口定义(保持不变) public interface IObjectWithSecurity { public bool AllowAccess { get; set; } }
效果验证
当你调用:
var item = await _itemRepository.GetAll() .IncludeCheckingSecurity(i => i.Documents) .SingleAsync();
这个方法会自动帮你转换成等价于:
var item = await _itemRepository.GetAll() .Include(i => i.Documents.Where(a => a.AllowAccess)) .SingleAsync();
的查询,EF Core会把这个过滤条件翻译成SQL,只返回AllowAccess为true的Documents集合。
注意事项
- EF Core版本要求:这个功能依赖EF Core 5.0及以上版本,因为EF Core 5才正式支持在
Include的Lambda中直接过滤集合。 - 类型兼容性:代码中加入了
Expression.Convert确保返回类型匹配原有的TThisInclude,避免因为集合类型(比如DbSet<T>和IQueryable<T>)的差异导致的类型错误。 - 逻辑扩展性:如果后续需要修改权限过滤逻辑,只需要调整
filterExpr的构造部分即可,不用修改所有调用的地方。
备注:内容来源于stack exchange,提问作者sjones
相关产品推荐
相关产品推荐

