如何组合两个表达式?

发布于 2024-11-26 12:10:09 字数 1350 浏览 6 评论 0原文

我正在尝试构建一个将应用于 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());
}

关于如何执行此操作有什么建议吗?

顺便说一句,将解析表达式的提供程序是 Linq for NHibernate。

I am trying to build up an expression that will be applied to an IQueryable collection.

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

喜爱皱眉﹌ 2024-12-03 12:10:09

看一下您的两个表达式树:

                 |                                      |
               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) 的主体以及所有出现的 x< /code> 与 z。然后,将结果包装在带有参数 z 的新 lambda 表达式中。

您可以使用 ExpressionVisitor 类 重写树,以及用于创建新的 lambda 表达式。

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. Then you wrap the result in a new lambda expression with parameter z.

You can use the ExpressionVisitor Class to rewrite the tree, and the Expression.Lambda Method to create the new lambda expression.

在梵高的星空下 2024-12-03 12:10:09

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

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

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

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:

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.

沧桑㈠ 2024-12-03 12:10:09

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

Following the suggestion from dtb and Marc that I use the ExpressionVisitor to rewrite the expression tree, this was the cleanest I could manage:

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());
}
叫嚣ゝ 2024-12-03 12:10:09

尚未完成,这里是一个可以使用 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;
    }
}

}

Out of completion, here is a version ready to work with 2, 3, n expressions

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

}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文