从二进制表达式到Expression<Func<T, bool>>

23

假设我有类似以下的内容

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的字符串表示,是否有办法从此字符串中获取与Expression中同名方法相同的方法?通常情况下,如果比较方法的名称以string形式给出,如何从该字符串实际调用Expression类上具有相同名称的方法?

如果需要的话,它必须与LINQ to Entities一起使用。


您可以使用ExpressionVisitor类更改表达式树。 - Steven
2个回答

22

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

那么,第一段代码中不需要硬编码 Expression.GreaterThan,而是可以使用类似于 wordToExpression["GreaterThan"](left, right) 这样的方式。

当然,这也可以通过反射的标准方式来实现。


8
当您使用GreaterThan时,需要指定表达式bodies,而不是lambda本身。不幸的是,有一个问题:两个表达式中的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”,Java中没有内置的方法,但您可以使用反射来实现:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接