23

Suppose I have something like

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) How can I replace the right hand of the assignment of the last line with something that uses the binaryExpression instead? var predicate = x => binaryExpression; doesn't work.

2) The right is always a constant, not necessarily DateTime.Now. Could it be of some simpler Expression type? For instance, it doesn't depend on SomeType, it is just a constant.

3) If I have the GreaterThan as a string, is there a way to get from this string to the method with the same name in Expression? In general, if the name of the comparison method is given as a string, how can I go from the string to actually calling the method with the same name on the Expression class?

It has to work with LINQ to Entities, if it matters.

pinkfloydhomer
  • 873
  • 3
  • 9
  • 16

2 Answers2

21

1 and 2: You need to build the expression tree manually to do this, the compiler cannot help because it only constructs ready-made expressions that represent functions in their entirety. That's not useful when you want to build functions piece by piece.

Here's one straightforward way to build the expression you want:

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

You can take this for a test drive with

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

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

3: Since in all likelihood the number of possible choices for your binary predicate will be quite small, you could do this with a dictionary:

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

Then, instead of hardcoding Expression.GreaterThan in the first snippet you would do something like wordToExpression["GreaterThan"](left, right).

Of course this can also be done the standard way with reflection.

Jon
  • 428,835
  • 81
  • 738
  • 806
8

When you use GreaterThan, you need to specify the expression bodies, not the lambda itself. Unfortunately, there is a complication: the x in the two expressions is not the same.

In this particular case, you could just about get away with this, because the second expression does not use x

So; your "1" and "2" should be answered by:

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

However, to handle this in the general case, you must rewrite on of the expressions, to fix-up the 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);

with:

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

For your "3"; there is nothing inbuilt for that; you could use reflection, though:

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);
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900