如何组合两个表达式?

时间:2011-07-28 06:11:09

标签: c# linq linq-to-nhibernate

我正在尝试构建一个将应用于IQueryable集合的表达式。

我可以构建一个这样的表达式:

[TestClass]
public class ExpressionTests
{
    private IQueryable<MyEntity> entities;

    private class MyEntity
    {
        public string MyProperty { get; set; }
    }

    [TestInitialize]
    public void Setup()
    {
        entities = new[]
                    {
                        new MyEntity {MyProperty = "first"}, 
                        new MyEntity {MyProperty = "second"}
                    }.AsQueryable();
    }

    [TestMethod]
    public void TestQueryingUsingSingleExpression()
    {
        Expression<Func<MyEntity, bool>> expression = e => e.MyProperty.Contains("irs");
        Assert.AreEqual(1, entities.Where(expression).Count());
    }
}

现在我想分开表达式的两个部分:

[TestMethod]
public void TestQueryingByCombiningTwoExpressions()
{
    Expression<Func<MyEntity, string>> fieldExpression = e => e.MyProperty;
    Expression<Func<string, bool>> operatorExpression = e => e.Contains("irs");
    // combine the two expressions somehow...
    Expression<Func<MyEntity, bool>> combinedExpression = ???;

    Assert.AreEqual(1, entities.Where(combinedExpression).Count());
}

关于我如何做到这一点的任何建议?

Btw解析表达式的提供程序是Linq for NHibernate。

4 个答案:

答案 0 :(得分:5)

看看你的两个表达树:

                 |                                      |
               Lambda                                Lambda
              /      \                              /      \
             /        \                            /        \
     Property          Parameter x               Call        Parameter y
    /        \                                  /  |  \
   /          \                                /   |   \
  x           MyProperty              EndsWidth    y    Constant
                                                        |
                                                       "5"

您需要创建一个如下所示的新树:

                                 |
                               Lambda
                              /      \
                             /        \
                           Call        Parameter z
                          /  |  \
                         /   |   \
                   EndsWith  |   Constant
                             |         \
                          Property     "5"
                         /        \
                        /          \
                       z          MyProperty

您可以轻松地看到新树的哪些部分来自哪个原始树。

要创建树,您将获取第二个lambda表达式(Call)的主体,并将所有出现的y替换为第一个lambda表达式(Property)的主体以及所有出现的xz。然后将结果包装在带有参数z的新lambda表达式中。

您可以使用ExpressionVisitor Class重写树,使用Expression.Lambda Method创建新的lambda表达式。

答案 1 :(得分:2)

这取决于提供商支持的内容;如果它支持子表达式(LINQ-to-SQL没有,EF不支持;我不知道NH),那么:

var combinedExpression = Expression.Lambda<Func<MyEntity, bool>>(
       Expression.Invoke(operatorExpression, fieldExpression.Body),
       fieldExpression.Parameters);

但是,如果不是,则需要使用ExpressionVisitor来合并它们。

答案 2 :(得分:1)

根据dtb和Marc的建议,我使用ExpressionVisitor重写表达式树,这是我能够管理的最干净的:

public class ExpressionBuilder<T> : ExpressionVisitor where T : class
{
    private Expression fieldExpressionBody;

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return fieldExpressionBody;
    }

    public Expression<Func<T, bool>> Build(
        Expression<Func<T, string>> fieldExpression,
        Expression<Func<string, bool>> operatorExpression)
    {
        fieldExpressionBody = fieldExpression.Body;
        Expression newExpressionBody = Visit(operatorExpression.Body);
        return Expression.Lambda<Func<T, bool>>(newExpressionBody, fieldExpression.Parameters[0]);
    }
}

在我的单元测试中使用它:

[TestMethod]
public void TestQueryingByCombiningTwoExpressions()
{
    Expression<Func<MyEntity, string>> fieldExpression = e => e.MyProperty;
    Expression<Func<string, bool>> operatorExpression = o => o.Contains("irs");

    var builder = new ExpressionBuilder<MyEntity>();
    Expression<Func<MyEntity, bool>> combinedExpression = builder.Build(fieldExpression, operatorExpression);
    Assert.AreEqual(1, entities.Where(combinedExpression).Count());
    Assert.AreEqual("e => e.MyProperty.Contains(\"irs\")", combinedExpression.ToString());
}

答案 3 :(得分:0)

完成之后,这是一个可以使用2,3或n个表达式的版本

public class ExpressionMerger : ExpressionVisitor
{
    Expression CurrentParameterExpression { get; set; }

    public Expression<Func<TIn, TOut>> Merge<TIn, TA, TOut>(Expression<Func<TIn, TA>> inner, Expression<Func<TA, TOut>> outer)
    {
        return MergeAll<TIn, TOut>(inner, outer);
    }

    public Expression<Func<TIn, TOut>> Merge<TIn, TA, TB, TOut>(Expression<Func<TIn, TA>> inner, Expression<Func<TA, TB>> transition, Expression<Func<TB, TOut>> outer)
    {
        return MergeAll<TIn, TOut>(inner, transition, outer);
    }

    protected Expression<Func<TIn, TOut>> MergeAll<TIn, TOut>(params LambdaExpression[] expressions)
    {
        CurrentParameterExpression = expressions[0].Body;

        foreach (var expression in expressions.Skip(1))
        {
            CurrentParameterExpression = Visit(expression.Body);
        }

        return Expression.Lambda<Func<TIn, TOut>>(CurrentParameterExpression, expressions[0].Parameters[0]);
    } 

    protected override Expression VisitParameter(ParameterExpression node)
    {
        //replace current lambda parameter with ~previous lambdas
        return CurrentParameterExpression;
    }
}

}