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

泛型仓储模式下如何实现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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 04:45:27