过滤集合元素的动态where子句

Dynamic where clause to filter collection elements

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

An exception of type 'System.InvalidOperationException' occurred in System.Core.dll but was not handled in user code Additional information: variable 'x' of type 'System.Collections.Generic.List`1[System.Object]' referenced from scope '', but it is not defined

我发现了类似的问题,我知道问题出在哪里,但我需要帮助才能找到解决方案。

这是我的代码:

        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());
            //
        }

我有一个类似的用例需要动态 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;
}

您不能在新表达式中使用来自另一个表达式 'as is' 的参数。当你这样做时:

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.[=15 时才在重写的方法中替换=]

顺便说一句:如果无论如何都在最后编译,为什么还需要表达式树?