简体   繁体   English

如何组合两个表达式?

[英]How do I combine two Expressions?

I am trying to build up an expression that will be applied to an IQueryable collection. 我正在尝试构建一个将应用于IQueryable集合的表达式。

I can build an expression like this: 我可以构建一个这样的表达式:

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

Now I want to separate the two parts of the expression: 现在我想分开表达式的两个部分:

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

Any suggestions as to how I might do this? 有关如何做到这一点的任何建议?

Btw the provider that will be resolving the expression is Linq for NHibernate. Btw将解析表达式的提供程序是Linq for NHibernate。

Take a look at your two expression trees: 看看你的两个表达式树:

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

You need to create a new tree that looks like this: 您需要创建一个如下所示的新树:

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

You can easily see what parts of the new tree come from which original tree. 您可以轻松地看到新树的哪些部分来自哪个原始树。

To create the tree, you take the body of the second lambda expression (Call) and replace all occurrences of y with the body of the first lambda expression (Property) and all occurrences of x with z . 要创建树,可以使用第二个lambda表达式(Call)的主体,并将所有出现的y替换为第一个lambda表达式(Property)的主体,并将所有出现的x替换为z Then you wrap the result in a new lambda expression with parameter z . 然后将结果包装在带有参数z的新lambda表达式中。

You can use the ExpressionVisitor Class to rewrite the tree, and the Expression.Lambda Method to create the new lambda expression. 您可以使用ExpressionVisitor类重写树,使用Expression.Lambda方法创建新的lambda表达式。

It depends on what the provider supports; 这取决于提供商支持的内容; if it supports sub-expressions (LINQ-to-SQL does, EF doesn't; I don't know about NH), then: 如果它支持子表达式(LINQ-to-SQL没有,EF不支持;我不知道NH),那么:

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

however, if it doesn't you'll need to use ExpressionVisitor to merge them. 但是,如果不是,则需要使用ExpressionVisitor来合并它们。

Following the suggestion from dtb and Marc that I use the ExpressionVisitor to rewrite the expression tree, this was the cleanest I could manage: 根据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]);
    }
}

And using it in my unit test: 并在我的单元测试中使用它:

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

Out of completion, here is a version ready to work with 2, 3, n expressions 完成之后,这是一个可以使用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;
    }
}

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM