泛型仓储模式下如何实现EF最新版本的导航属性过滤?
关联查询过滤+仓储解耦的实现方案
现有问题
我已经搞定了用点路径表示法把关联导航转成EF的.Include()/.ThenInclude(),支持多层关联查询,但现在EF出了带过滤的Include(比如Include(x => x.MyNavigations.Where(predicate))),点路径根本搞不定——因为点路径只能描述导航层级,带不了过滤逻辑。核心需求是:仓储层不能绑死EF,但又要能在关联查询里加自定义过滤。
基于原ThenInclude方案的扩展实现
之前的ThenInclude方案是靠解析表达式树把点路径转成EF的链式调用,现在可以在这个基础上扩展成用表达式树传递通用过滤规则,具体步骤如下:
1. 定义不依赖EF的过滤契约
先整个抽象类,用来存导航路径和对应的过滤条件,完全不碰EF的东西:
public class IncludeFilter<TEntity> { // 导航路径表达式,比如 x => x.MyNavigations public Expression<Func<TEntity, object>> NavigationPath { get; set; } // 导航集合的过滤表达式 public LambdaExpression Filter { get; set; } }
2. 仓储接口抽象
仓储接口里只放通用方法,绝对不出现EF相关类型:
public interface IRepository<TEntity> where TEntity : class { IQueryable<TEntity> GetAllWithIncludes(IEnumerable<IncludeFilter<TEntity>> includeFilters = null); }
3. EF实现层做适配
在EF的仓储实现里,把IncludeFilter转成EF带过滤的Include调用,这一步才碰EF:
public class EfRepository<TEntity> : IRepository<TEntity> where TEntity : class { private readonly DbContext _context; public EfRepository(DbContext context) => _context = context; public IQueryable<TEntity> GetAllWithIncludes(IEnumerable<IncludeFilter<TEntity>> includeFilters = null) { var query = _context.Set<TEntity>().AsQueryable(); if (includeFilters == null) return query; foreach (var filter in includeFilters) { // 反射获取EF的Include方法,拼接导航+过滤表达式 var includeMethod = typeof(EntityFrameworkQueryableExtensions) .GetMethods() .First(m => m.Name == "Include" && m.GetParameters().Length == 2) .MakeGenericMethod(typeof(TEntity), filter.NavigationPath.ReturnType.GetGenericArguments()[0]); // 组合导航路径和过滤表达式,复用原ThenInclude方案的表达式解析逻辑 query = (IQueryable<TEntity>)includeMethod.Invoke( null, new object[] { query, Compose(filter.NavigationPath, filter.Filter) } ); } return query; } // 表达式拼接辅助方法:把导航路径和过滤逻辑组合成带Where的导航表达式 private static Expression<Func<TEntity, IEnumerable<TNav>>> Compose<TNav>( Expression<Func<TEntity, object>> navigation, LambdaExpression filter) { var navExpr = navigation.Body as MemberExpression; var param = Expression.Parameter(typeof(TEntity)); var navAccess = Expression.PropertyOrField(param, navExpr.Member.Name); var whereCall = Expression.Call( typeof(Enumerable), "Where", new[] { typeof(TNav) }, navAccess, Expression.Invoke(filter, Expression.Parameter(typeof(TNav))) ); return Expression.Lambda<Func<TEntity, IEnumerable<TNav>>>(whereCall, param); } }
4. 业务层调用示例
业务层完全不用管EF,直接用抽象的IncludeFilter传规则就行:
var includeFilters = new List<IncludeFilter<Order>> { new() { NavigationPath = o => o.OrderItems, Filter = (Expression<Func<OrderItem, bool>>)(oi => oi.Price > 100) } }; var orders = _orderRepository.GetAllWithIncludes(includeFilters).ToList();
核心好处
- 彻底解耦:仓储接口和EF无关,哪天换ORM改实现层就行,不用动业务代码
- 支持复杂过滤:不止Where,后续还能扩展加OrderBy、Take这类针对关联集合的操作
- 兼容老代码:原来的点路径方案可以保留当简化版用,和过滤方案共存
内容的提问来源于stack exchange,提问作者user15716642
相关产品推荐
相关产品推荐

