理解表达式树和参数求值

本文关键字:参数 表达式 | 更新日期: 2023-09-27 18:00:12

我正试图修改一个表达式树,该树动态构建Contains表达式,最终生成类似SQL的

P IN (123, 124, 125, 200, 201)

相反,检查执行范围检查,这最终会导致类似SQL的

(P >= 123 AND P <= 125) OR (P >= 200 AND P <= 201)

我的解决方案基于这个帖子。

static public Expression<Func<TElement, bool>> 
BuildContainsExpression<TElement, TValue>(
    Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
{
    // Removed for post: Input checks and edge cases
    var equals = 
      values.Select(value => 
       (Expression)Expression.Equal(valueSelector.Body, 
                                    Expression.Constant(value, typeof(TValue))));
    var body = equals.Aggregate<Expression>((accumulate, equal) => 
                                             Expression.Or(accumulate, equal));
    return Expression.Lambda<Func<TElement, bool>>(body, p);
}

如果我提供比较值,我就可以进行范围检查:

long testValue = 5;
List<KeyValuePair<int, int>> ranges = new List<KeyValuePair<int, int>>() 
{
    new KeyValuePair<long, long>(3, 6),
    new KeyValuePair<long, long>(10, 12),
    new KeyValuePair<long, long>(20, 20),
};
List<BinaryExpression> rangeExpressions = new List<BinaryExpression>();
foreach (var pair in ranges)
{
    var greaterThanOrEqual = 
        Expression.GreaterThanOrEqual(Expression.Constant(testValue), 
                                      Expression.Constant(pair.Key));
    var lessThanOrEqual = 
        Expression.LessThanOrEqual(Expression.Constant(testValue), 
                                   Expression.Constant(pair.Value));
    var inRange = Expression.AndAlso(greaterThanOrEqual, lessThanOrEqual);
    rangeExpressions.Add(inRange);
}
var final = 
    rangeExpressions.Aggregate<Expression>((a, b) => Expression.Or(a, b));
var result = Expression.Lambda<Func<bool>>(final).Compile()();

然而,当我将代码放入要与Linq一起使用的方法中时,我无法从传入的表达式中找出如何获得用于比较的值。该方法的特征是:

Expression<Func<TElement, bool>> 
BuildRangeExpression<TElement>(
    Expression<Func<TElement, long>> valueSelector, 
    IEnumerable<long> values)

它的用法类似于:

Expression<MyType, bool> match = 
    BuildRangeExpression<MyType, long>(my => my.ProductCode, productCodes);
var result = db.MyTypes.Where(match);

问题

如何评估

Expression<Func<TElement, long>> valueSelector

以便我可以使用传递到BuildRangeExpression中的值,而不是我当前的硬编码值

long testValue = 5;

理解表达式树和参数求值

我认为博客文章中的代码正是您所需要的:您所要做的就是使用valueSelector.Body而不是Expression.Constant(),并将原始参数添加到生成的表达式中:

public static Expression<Func<TElement, bool>>
    BuildRangeExpression<TElement, TValue>(
    Expression<Func<TElement, TValue>> valueSelector,
    IEnumerable<Tuple<TValue, TValue>> values)
{
    var p = valueSelector.Parameters.Single();
    var equals = values.Select(
        tuple =>
        Expression.AndAlso(
            Expression.GreaterThanOrEqual(
                valueSelector.Body, Expression.Constant(tuple.Item1)),
            Expression.LessThanOrEqual(
                valueSelector.Body, Expression.Constant(tuple.Item2))));
    var body = equals.Aggregate(Expression.OrElse);
    return Expression.Lambda<Func<TElement, bool>>(body, p);
}

使用Expression.Parameter

创建一个参数:

var param = Expression.Parameter(typeof(TElement), "arg")

您将需要放置param,而不是Expression.Constant(testvalue)

然后,你需要做:

var result = Expression.Lambda<Func<TElement, bool>>(final, param).Compile()