29

我从 MSDN 的文章中了解到如何:修改表达式树ExpressionVisitor应该做什么。它应该修改表达式。

然而,他们的例子非常不切实际,所以我想知道为什么我需要它?您能否列举一些修改表达式树有意义的真实案例?或者,为什么必须对其进行修改?从什么到什么?

它还有许多用于访问各种表达式的重载。我怎么知道什么时候应该使用它们以及它们应该返回什么?另一方面,我看到人们使用VisitParameter和返回另一个正在返回。base.VisitParameter(node)Expression.Parameter(..)

4

4 回答 4

16

存在一个问题,在数据库中我们有包含 0 或 1(数字)的字段,我们想在应用程序上使用布尔值。

解决方案是创建一个“标志”对象,其中包含 0 或 1 并转换为布尔值。我们在所有应用程序中都像 bool 一样使用它,但是当我们在 .Where() 子句中使用它时,EntityFramework 抱怨它无法调用转换方法。

因此,在将树发送到 EF 之前,我们使用表达式访问者将所有属性访问(如 .Where(x => x.Property) 更改为 .Where(x => x.Property.Value == 1))。

于 2017-01-14T07:13:06.267 回答
14

您能否列举一些修改表达式树有意义的真实案例?

严格来说,我们从不修改表达式树,因为它们是不可变的(至少从外部看,它不会在内部记忆值或具有可变的私有状态)。正是因为它们是不可变的,因此我们不能仅仅改变一个节点,如果我们想要创建一个基于我们拥有但在某些特定方式上有所不同的新表达式树(最接近我们必须修改不可变对象的事情)。

我们可以在 Linq 本身中找到一些。

在许多方面,最简单的 Linq 提供程序是 linq-to-objects 提供程序,它适用于内存中的可枚举对象。

当它直接接收作为IEnumerable<T>对象的枚举时,它非常简单,因为大多数程序员可以很快编写大多数方法的未优化版本。例如Where只是:

foreach (T item in source)
  if (pred(item))
    yield return item;

等等。但是如何EnumerableQueryable实现这些IQueryable<T>版本呢?由于EnumerableQueryable包装IEnumerable<T>我们可以对所涉及的一个或多个可枚举对象执行所需的操作,但是我们有一个表达式描述该操作,IQueryable<T>以及选择器、谓词等的其他表达式,我们需要的是对该操作的描述在IEnumerable<T>选择器、谓词等方面和委托方面。

System.Linq.EnumerableRewriterExpressionVisitor正是这样一个重写的实现,然后可以简单地编译和执行结果。

在其内部System.Linq.Expressions,有一些ExpressionVisitor用于不同目的的实现。一个例子是编译的解释器形式不能直接处理引用表达式中的提升变量,因此它使用访问者将其重写为处理索引到字典中。

除了产生另一个表达式之外,anExpressionVisitor还可以产生另一个结果。同样System.Linq.Expressions有内部示例,调试字符串和ToString()许多表达式类型通过访问有问题的表达式来工作。

这可以(尽管不一定)是数据库查询 linq 提供程序用来将表达式转换为 SQL 查询的方法。

我怎么知道什么时候应该使用它们以及它们应该返回什么?

这些方法的默认实现将:

  1. 如果表达式不能有子表达式(例如 的结果Expression.Constant()),那么它将再次返回节点。
  2. 否则访问所有子表达式,然后调用Update有问题的表达式,将结果传回。Update反过来,将返回与新子节点相同类型的新节点,或者如果子节点没有更改,则再次返回相同的节点。

因此,如果您不知道出于任何目的需要在节点上显式操作,那么您可能不需要更改它。这也意味着这Update是获取新版本节点以进行部分更改的便捷方式。但是,“无论你的目的是什么”意味着什么当然取决于用例。最常见的情况可能会走到一个极端或另一个极端,只有一种或两种表达式类型需要覆盖,或者全部或几乎全部都需要它。

(一个警告是,如果您正在检查那些在 a 中具有子节点的节点的子节点,ReadOnlyCollection例如BlockExpression其步骤和变量或其TryExpression捕获块,并且您有时只会更改这些子节点,那么如果您没有更改,那么您就是最好自己检查这是一个缺陷[最近修复,但还没有在任何发布的版本中]意味着如果你将相同的孩子传递Update到与原始集合不同的集合中,ReadOnlyCollection那么会不必要地创建一个新的表达式,这会进一步影响树。这通常是无害的,但会浪费时间和内存)。

于 2017-01-14T18:27:53.993 回答
6

ExpressionVisitor启用's的访问者模式Expression

从概念上讲,问题是当你浏览一Expression棵树时,你只知道任何给定的节点都是一个Expression,但你不知道具体是什么类型的ExpressionExpression此模式允许您知道您正在使用哪种类型,并为不同类型指定特定于类型的处理。

当你有一个Expression,你可以打电话.Modify。知道自己的Expression类型,所以它会回调适当的override.

查看您链接的 MSDN 示例

public class AndAlsoModifier : ExpressionVisitor  
{  
    public Expression Modify(Expression expression)  
    {  
        return Visit(expression);  
    }  

    protected override Expression VisitBinary(BinaryExpression b)  
    {  
        if (b.NodeType == ExpressionType.AndAlso)  
        {  
            Expression left = this.Visit(b.Left);  
            Expression right = this.Visit(b.Right);  

            // Make this binary expression an OrElse operation instead of an AndAlso operation.  
            return Expression.MakeBinary(ExpressionType.OrElse, left, right, b.IsLiftedToNull, b.Method);  
        }  

        return base.VisitBinary(b);  
    }  
}

在此示例中,如果Expression恰好是 a BinaryExpression,它将回调VisitBinary(BinaryExpression b)示例中给出的。现在,您BinaryExpression知道它是一个BinaryExpression. 您还可以指定override处理其他类型的其他方法Expression

值得注意的是,由于这是一个重载的解析技巧,visitedExpression将回调最佳拟合方法。所以,如果有不同种类的BinaryExpression's,那么你可以override为一个特定的子类型编写一个;如果另一个子类型回调,它将只使用默认BinaryExpression处理。

简而言之,这种模式允许您在Expression树中导航,了解Expression您正在使用的 ' 类型。

于 2017-01-14T17:06:51.947 回答
2

在转移到 EF Core 并从 Sql Server(MS 特定)迁移到 SqlLite(独立于平台)时,我刚刚遇到了具体的现实世界示例。

现有的业务逻辑围绕着一个中间层/服务层接口,假设全文搜索 (FTS) 在后台自动发生,就像它与 SQL Server 一样。搜索相关查询通过表达式和 FTS 针对 Sql Server 存储传递到此层,不需要额外的 FTS 特定实体。

我不想更改任何内容,但是使用 SqlLite,您必须针对全文搜索的特定虚拟表,这反过来意味着更改所有中间层调用以重新定位 FTS 表/实体,然后加入将它们添加到业务实体表中以获得类似的结果集。

但是通过对 ExpressionVisitor 进行子类化,我能够拦截 DAL 层中的调用并简单地重写传入表达式(或更准确地说是整个搜索表达式中的一些 BinaryExpressions)来专门处理 SqlLites FTS 要求。

这意味着数据层对数据存储的专门化发生在从存储库基类中的单个位置调用的单个类中。无需更改应用程序的其他方面即可通过 EFCore 支持 FTS,并且任何 SqlLite FTS 相关实体都可以包含在单个可插入程序集中。

所以 ExpressionVisitor 真的非常有用,特别是结合能够通过各种形式的 IPC 将表达式树作为数据传递的整个概念时。

于 2018-04-13T23:30:11.467 回答