动态where子句用于过滤集合元素

时间:2015-05-12 14:35:17

标签: c# linq expression-trees

我有一个集合List<List<object>>,我需要根据List<object>集合包含给定元素的过滤器来过滤掉它。我能够构建where子句,但是我得到以下异常:

  

类型&#39; System.InvalidOperationException&#39;的例外情况发生在System.Core.dll中但未在用户代码中处理   附加信息:变量&#39; x&#39;类型&#39; System.Collections.Generic.List`1 [System.Object]&#39;引用范围&#39;&#39;,但未定义

我发现了类似的问题,我明白问题所在,但我需要帮助找到解决方案。

这是我的代码:

        protected override Expression<Func<List<object>, bool>> GetWhereClause()
        {
            var type = typeof(List<object>);
            var parameterExpression = Expression.Parameter(type, "x");                

            Expression expressionBody = null;
            if (Verified)
            {
                Expression<Func<List<object>, bool>> expression = x => x.Contains("Verified");
                expressionBody = expression.Body;
            }
            if (GoodMatch)
            {
                Expression<Func<List<object>, bool>> expression = x => x.Contains("Good Match");
                if (expressionBody != null)
                    expressionBody = Expression.Or(expressionBody, expression.Body);
                else
                    expressionBody = expression.Body;
            }
            //More conditions here
            if (expressionBody != null)
            {
                var whereClauseExp = Expression.Lambda<Func<List<object>, bool>>(expressionBody, parameterExpression);

                return whereClauseExp;
            }

            return null;
        } 

现在,这个方法生成了所需的where子句,但是当我尝试应用它时,我得到了提到的异常。

        if (whereClause != null)
        {
            items = items.Where(whereClause.Compile());
            //
        }

2 个答案:

答案 0 :(得分:1)

我有一个类似的用例需要动态where子句并使用Predicate Builder

使用它,您可以执行以下操作:*

private Expression<Func<List<T>, bool>> GetWhereClause<T>(T itemToFind){
        var predicate = PredicateBuilder.False<List<T>>();

        if(Verified) {
            predicate = predicate.And(p => p.Contains(itemToFind));
        }

        if(GoodMatch) {
            predicate = predicate.Or(p => p.Contains(itemToFind));
        }

        return predicate;
}

答案 1 :(得分:0)

您不能在新表达式中“按原样”使用其他表达式中的参数。当你这样做时:

Expression<Func<List<object>, bool>> expression = x => x.Contains("Verified");
expressionBody = expression.Body;

然后你只需要在正文中定义内联参数x。现在这个参数不是你以前定义为

的参数
var parameterExpression = Expression.Parameter(type, "x"); 

即使他们都有名字x,这还不够。表达式树具有引用相等性。 因此,要使其工作,只需使用访问者,这将取代您的参数。创建访问者:

public class ParameterUpdateVisitor : ExpressionVisitor
{
    private ParameterExpression _parameter;

    public ParameterUpdateVisitor(ParameterExpression parameter)
    {
        _parameter = parameter;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return _parameter;
    }
}

然后在你的代码中使用它:

Expression<Func<List<object>, bool>> expression = x => x.Contains("Verified");
var visitor = new ParameterUpdateVisitor(parameterExpression);
expressionBody = visitor.Visit(expression.Body);

当然这适用于来自另一个表达式树的每个部分。

请注意!!!这个访客是额外的简化,只是为了你的例子。如果您的表达式可能包含具有自己参数的方法,那么请确保仅替换您想要的参数! 例如。它不适用于:

Expression<Func<List<object>, bool>> expression = x => x.Select(o => o.ToString()).Contains("Verified");

因为这位访客也将取代'o'。如果你有这种情况,那么也要在构造函数中传入你想要替换的参数(例如x,即expression.Parameters.First()),并且只在node == myOldParameter中替换覆盖的方法。

顺便说一句:如果你最后编译那个东西,为什么还需要表达式树呢?