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

将返回IEnumerable的动态LINQ筛选函数改为返回IQueryable

问题分析与解决方案

首先,你的核心思路完全正确:应该返回IQueryable<T>而非IEnumerable<T>,这样就能把筛选和排序的表达式树组合起来,最后一次性执行查询,避免中途编译表达式导致的内存操作(IEnumerable的Where是在内存中过滤,而IQueryable的Where会把表达式传递给数据源执行,比如EF Core会转成SQL)。

你遇到的报错,主要是因为表达式树构建过程中存在几个细节错误,下面一步步解决:

1. 修复表达式树构建的错误

1.1 EndsWith方法调用参数缺失

你在ExpressionRetriever中获取的endsWithMethod是接受两个参数(string和StringComparison)的重载,但在case Comparison.EndsWith时只传了constant,少了comparisonType参数,这会导致表达式树无效,运行时抛出异常。修改如下:

case Comparison.EndsWith: 
    return Expression.Call(member, endsWithMethod, constant, comparisonType);

1.2 BoolTest可以简化为Expression.Equal

你当前用booleanEqualMethod来调用Equals方法,其实直接用Expression.Equal更简洁,也避免了方法获取可能的问题:

case Comparison.BoolTest: 
    return Expression.Equal(member, Expression.Constant(Convert.ToBoolean(filter.Value), typeof(bool)));

1.3 处理类型不匹配的情况

如果filter.Value的类型和属性类型不一致(比如属性是int,但Value是string),直接构建表达式会报错,需要添加类型转换:

ConstantExpression constant = Expression.Constant(Convert.ChangeType(filter.Value, member.Type), member.Type);

1.4 空筛选列表的处理

当前代码在filters.Count == 0时返回null,这会导致后续排序调用空引用,应该返回原数据源:

if (filters.Count == 0) return source;

2. 修改ConstructAndExpressionTree返回IQueryable<T>

去掉Compile()调用,直接返回IQueryable<T>,这样就能和排序的表达式树组合:

public static IQueryable<T> ConstructAndExpressionTree<T>(IQueryable<T> source, List<ExpressionFilter> filters)
{
    if (filters.Count == 0) return source;

    ParameterExpression param = Expression.Parameter(typeof(T), "t");
    Expression exp = ExpressionRetriever.GetExpression<T>(param, filters[0]);

    for (int i = 1; i < filters.Count; i++)
    {
        exp = Expression.And(exp, ExpressionRetriever.GetExpression<T>(param, filters[i]));
    }

    var lambda = Expression.Lambda<Func<T, bool>>(exp, param);
    return source.Where(lambda);
}

3. 完整修改后的ExpressionRetriever

public static class ExpressionRetriever
{
    private static MethodInfo containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string), typeof(StringComparison) });
    private static MethodInfo startsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string), typeof(StringComparison) });
    private static MethodInfo endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string), typeof(StringComparison) });
    private static MethodInfo indexOfMethod = typeof(string).GetMethod("IndexOf", new Type[] { typeof(string), typeof(StringComparison) });

    public static Expression GetExpression<T>(ParameterExpression param, ExpressionFilter filter)
    {
        MemberExpression member = Expression.Property(param, filter.PropertyName);
        // 处理类型转换,确保Value和属性类型匹配
        var convertedValue = Convert.ChangeType(filter.Value, member.Type);
        ConstantExpression constant = Expression.Constant(convertedValue, member.Type);
        ConstantExpression comparisonType = Expression.Constant(StringComparison.OrdinalIgnoreCase);

        switch (filter.Comparison)
        {
            case Comparison.Equal: 
                return Expression.Equal(member, constant);
            case Comparison.GreaterThan: 
                return Expression.GreaterThan(member, constant);
            case Comparison.GreaterThanOrEqual: 
                return Expression.GreaterThanOrEqual(member, constant);
            case Comparison.LessThan: 
                return Expression.LessThan(member, constant);
            case Comparison.LessThanOrEqual: 
                return Expression.LessThanOrEqual(member, constant);
            case Comparison.NotEqual: 
                return Expression.NotEqual(member, constant);
            case Comparison.Contains: 
                return Expression.Call(member, containsMethod, constant, comparisonType);
            case Comparison.StartsWith: 
                return Expression.Call(member, startsWithMethod, constant, comparisonType);
            case Comparison.IndexOf: 
                return Expression.NotEqual(
                    Expression.Call(member, indexOfMethod, constant, comparisonType),
                    Expression.Constant(-1, typeof(int))
                );
            case Comparison.EndsWith: 
                return Expression.Call(member, endsWithMethod, constant, comparisonType);
            case Comparison.BoolTest: 
                return Expression.Equal(member, Expression.Constant(Convert.ToBoolean(filter.Value), typeof(bool)));
            default: 
                throw new ArgumentOutOfRangeException(nameof(filter.Comparison), filter.Comparison, null);
        }
    }
}

4. 组合筛选和排序的用法

现在你可以先调用筛选,再调用排序,最后执行查询(比如用ToList()):

// 示例:构建筛选条件
var filters = new List<ExpressionFilter>
{
    new ExpressionFilter { PropertyName = "Age", Value = 30, Comparison = Comparison.GreaterThan },
    new ExpressionFilter { PropertyName = "Name", Value = "J", Comparison = Comparison.StartsWith }
};

// 构建排序条件
var sortItems = new List<KeyValuePair<string, SortDirection>>
{
    new KeyValuePair<string, SortDirection>("Age", SortDirection.desc),
    new KeyValuePair<string, SortDirection>("Name", SortDirection.asc)
};

// 组合筛选和排序,最后执行
var query = DynamicFilteringHelper.ConstructAndExpressionTree(dbContext.Users.AsQueryable(), filters);
var sortedQuery = DynamicOrderingHelper.OrderByAndThenBy(query, sortItems);
var result = sortedQuery.ToList(); // 此时才会编译并执行表达式树

为什么之前的尝试报错?

除了上述的参数缺失问题,当你直接调用source.Where(Expression.Lambda<Func<T, bool>>(exp, param))时,如果表达式树本身有错误(比如EndsWith参数不足),就会抛出"Operation is not valid due to the current state of the object"异常,这是表达式树构建无效导致的运行时错误,而非IQueryable本身的问题。

现在修改后,表达式树是完整有效的,就能正常和排序的表达式树组合,最后一次性执行查询,充分利用IQueryable的延迟执行特性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:45:28