Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

From BinaryExpression to Expression<Func<T, bool>>

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.

like image 560
pinkfloydhomer Avatar asked May 29 '13 07:05

pinkfloydhomer


2 Answers

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.

like image 110
Jon Avatar answered Oct 15 '22 11:10

Jon


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);
like image 25
Marc Gravell Avatar answered Oct 15 '22 09:10

Marc Gravell