От двоичного выражения до выражения <Func<T, bool> >

Предположим, у меня есть что-то вроде

Expression<Func<SomeType, DateTime>> left = x => x.SomeDateProperty;
Expression<Func<SomeType, DateTime>> right = x => dateTimeConstant;
var binaryExpression = Expression.GreaterThan(left, right);
Expression<Func<SomeType, bool>> predicate = 
                          x => x.SomeDateProperty> dateTimeConstant;

1) Как я могу заменить правую часть назначения последней строки на что-то, что использует binaryExpression? var predicate = x => binaryExpression; не работает.

2) right всегда постоянное, не обязательно DateTime.Now. Может ли это быть более простым типом Expression? Например, это не зависит от SomeType, это просто константа.

3) Если у меня есть GreaterThan как string, есть ли способ получить из этой строки метод с тем же именем в Expression? В общем случае, если имя метода сравнения задано как string, как я могу перейти от строки к фактическому вызову метода с тем же именем в классе Expression?

Он должен работать с LINQ для Entities, если это имеет значение.

Ответ 1

1 и 2: вам нужно построить дерево выражений вручную, чтобы сделать это, компилятор не может помочь, потому что он только конструирует готовые выражения, которые представляют функции целиком. Это не полезно, когда вы хотите строить функции по частям.

Здесь один простой способ построить выражение, которое вы хотите:

var argument = Expression.Parameter(typeof(SomeType));
var left = Expression.Property(argument, "SomeDateProperty");
var right = Expression.Constant(DateTime.Now);

var predicate = Expression.Lambda<Func<SomeType, bool>>(
    Expression.GreaterThan(left, right),
    new[] { argument }
);

Вы можете взять это на тест-драйв с

var param = new SomeType { 
    SomeDateProperty = DateTime.Now.Add(TimeSpan.FromHours(-1))
};

Console.WriteLine(predicate.Compile()(param)); // "False"

3: Поскольку, по всей вероятности, количество возможных вариантов вашего бинарного предиката будет довольно небольшим, вы можете сделать это со словарем:

var wordToExpression = 
    new Dictionary<string, Func<Expression, Expression, BinaryExpression>>
{
    { "GreaterThan", Expression.GreaterThan },
    // etc
};

Затем вместо wordToExpression["GreaterThan"](left, right) Expression.GreaterThan в первом фрагменте вы сделаете что-то вроде wordToExpression["GreaterThan"](left, right).

Конечно, это также можно сделать стандартным способом с отражением.

Ответ 2

Когда вы используете GreaterThan, вам нужно указать тела выражения, а не лямбда. К сожалению, есть усложнение: x в двух выражениях не то же самое.

В этом конкретном случае вы могли бы просто уйти от этого, потому что второе выражение не использует x

Так; на ваши "1" и "2" следует ответить:

var binaryExpression = Expression.GreaterThan(left.Body, right.Body);
    var lambda = Expression.Lambda<Func<SomeType, bool>>(binaryExpression,
        left.Parameters);

Однако, чтобы справиться с этим в общем случае, вы должны переписать выражения, чтобы исправить параметры:

var binaryExpression = Expression.GreaterThan(left.Body,
    new SwapVisitor(right.Parameters[0], left.Parameters[0]).Visit(right.Body));
var lambda = Expression.Lambda<Func<SomeType, bool>>(binaryExpression,
    left.Parameters);

с:

public class SwapVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public SwapVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

Для вашего "3"; для этого нет ничего; вы могли бы использовать отражение, хотя:

string method = "GreaterThan";

var op = typeof(Expression).GetMethod(method,
    BindingFlags.Public | BindingFlags.Static,
    null, new[] { typeof(Expression), typeof(Expression) }, null);

var rightBody = new SwapVisitor(right.Parameters[0],
     left.Parameters[0]).Visit(right.Body);
var exp = (Expression)op.Invoke(null, new object[] { left.Body, rightBody });

var lambda = Expression.Lambda<Func<SomeType, bool>>(exp, left.Parameters);