【问题标题】:Expression Tree Dependency Analyzer表达式树依赖分析器
【发布时间】:2010-09-26 06:25:17
【问题描述】:

我正在为跨数据源 IQueryProvider 构建表达式树依赖分析器。

也就是说,我有一个带有一些元素的 IQueryable,这些元素可以在内存中针对某个任意提供者(例如实体框架)在本地执行。 IQueryable 中的其他一些元素违背了我需要进行远程 WCF 调用的实体。 WCF 操作采用序列化的表达式树,对其进行反序列化,针对其自己的本地数据存储(也可以说是实体框架)执行 LINQ 查询,然后将结果发回给我(尽管这种机制可以很容易地成为 WCF 数据服务DataServiceQuery ......但我没有使用它,因为它的功能支持水平是有限的......充其量)。从 WCF 服务返回结果后,我将在内存中针对本地执行的 LINQ 查询执行 LINQ 查询的结果。

那么,这有什么难的呢?好吧,我需要确定表达式树的依赖关系,以便我的本地底层查询提供程序不会在尝试执行我的 LINQ 查询时爆炸,该查询具有只能在远程 WCF 服务上执行的组件……反之亦然。

让我们来看一个简单的场景:

  var result = 
   (from entityX in new Query<MyEntityX>()
   from entityY in new Query<MyEntityY>()
   where entityX.SomeProperty == "Hello" &&
   entityY.SomeOtherProperty == "Hello 2" && entityX.Id == entityY.XId).ToList();

Query&lt;T&gt; 是一个简单的可查询包装器,它带有我自己的提供程序,它有机会解析树并找出在用不同的查询提供程序交换根之前要做什么。因此,在上述情况下,我需要:

  1. 使用本地对象上下文对 MyEntityA 执行查询并仅应用 myEntityX.SomeProperty == "Hello" 条件。即在本地运行以下命令:

    // assume the functionality for replacing new Query<MyEntityA> with new
    // ObjectContext<MyEntityA>() is already there...
    var resultX = (from entityX in new Query<MyEntityX>()
    where entityX.SomeProperty == "Hello").ToList().AsQueryable();

  2. 发送以下序列化并让它在我的远程 WCF 服务上执行,然后返回结果。

    // Send the preceeding expression over the over the wire
    // and get the results back (just take my word this already works)
    var resultY = (from entityY in new Query<MyEntityY>()
    where entityY.SomeOtherProperty == "Hello 2").ToList().AsQueryable();

  3. 在内存中执行以下:

    var finalResult = (from entityX in resultX
    from entityY in resultY
    where entityX.SomeProperty == "Hello" &&
    entityY.SomeOtherProperty == "Hello 2" &&
    entityX.Id == entityY.XId).ToList();

请注意,解决方案必须包含一种累积标准的方法,该标准也从投影中指定......就像

var result = 
(from i in  
  (from entityX in new Query<MyEntityX>()  
   from entityY in new Query<MyEntityY>()  
   select new { PropX = entityX, PropY = entityY })  
where  
   i.PropX.SomeProperty == "Hello" && i.PropY.SomeOtherProperty == "Hello 2"  
   && i.PropX.Id == i.PropY.XId  
select i)  
.ToList();

这应该会导致在内存中评估其余查询之前实际发出上述相同的两个单独的查询。顺便说一句,我想我可能会使用 PLINQ 和/或 DRYAD 来运行内存中的操作并提高性能。

所以,我有一些想法(比如与访问者一起在树上进行一些传递并为给定实体类型积累候选者),但我正在寻找其他人关于如何积累我的表达式树部分的建议可以针对给定的上下文执行...也就是说,知道一个条件适用于一个基础的新Query&lt;T&gt; 而另一个条件适用于另一个条件...这样我就可以弄清楚我可以对数据做什么存储 1,我可以对数据存储 2 做什么以及我需要在内存中做什么,并相应地执行树的不同部分。它有点像 Funcletizer,但更复杂一些......

感谢您的帮助。

【问题讨论】:

  • 我有点不清楚这里的问题是什么。你在问什么?
  • 我正在寻求有关如何确定表达式树的依赖关系的建议。也就是说,当访问树时,知道条件 LambdaExpression Where(x => x.SomeProperty == "Hello") 中的 MemberExpression x.SomeProperty 植根于 new Query() 中的 ConstantExpression。
  • 我查看了编辑历史。为什么要通过 WCF 发送表达式树?听起来你在试图保护程序员免受他们自己的伤害——它永远不会奏效。我的建议是将您的查询分解为组件并分别执行。
  • 为什么保护程序员免受他们自己的伤害永远行不通?这不是许多现代编程设计模式的原则吗?为什么我们不直接编写包含业务逻辑的存储过程来提供最佳性能和精细控制?
  • 因为抽象是有漏洞的,当你需要比抽象提供更多的控制时,它就会崩溃,失去所有好处。我并不是说抽象不好,但如果你有适当的关注点分离,它们就不成问题了。在您的情况下,听起来您需要从许多来源中提取数据。为什么不拥有一个知道如何查询各个来源、编译信息并将其返回给调用者的查询服务呢?推迟查询、拆分查询并针对不同的数据源运行其中的一部分是没有好的方法。

标签: c# linq lambda expression-trees


【解决方案1】:

这是一个有趣的问题。我会考虑一个由几个阶段组成的方法:

  1. 表达式树的表达式分析(可能是自下而上)并将节点标记为“远程”、“本地”和“中性”
  2. “远程”子表达式的自上而下识别
  3. 远程查询执行(子表达式消除)
  4. 本地查询执行

以下提供了每个阶段的更多详细信息。我的答案末尾的 备注 部分提供了一些需要考虑的重要说明。

免责声明:我的回答是相当示意性的,我确信它没有涵盖与表达式树中允许的单个操作的语义相关的很多方面和情况。我认为必须做出某些妥协才能使实现相当简单。

第一阶段:表达分析和标记

表达式树中的每个节点都可以被认为属于以下类别:

  • “远程”节点对应于必须远程执行的操作;
  • “本地”节点对应于必须在本地执行的操作;
  • “中性”节点对应于可由任何查询处理器执行的操作。

遍历和处理表达式树的自下而上方法似乎适合这种情况。原因是在处理给定节点 X 时,具有子节点 Y_1 到 Y_n,节点 X 的类别在很大程度上取决于其子节点 Y_1 到 T_n 的类别。

让我们重写您提供的示例:

entityX.SomeProperty == "Hello" &&
entityY.SomeOtherProperty == "Hello 2" && 
entityX.Id == entityY.Id

变成对应表达式树的轮廓:

&&(&&(==(Member(SomeProperty, Var(entityX)), "Hello"), 
      ==(Member(SomeOtherProperty, Var(entityY)), "Hello 2")),
   ==(Member(Id, Var(entityX)), Member(Id, Var(entityY)))

然后将自下而上标记此表达式树。 R 用于“远程”,L 用于“本地”,N 用于“中性”。如果entityX 是远程的,entityY 是本地的,结果将如下所示:

L:&&(L:&&(R:==(R:Member(SomeProperty, R:Var(entityX)), N:"Hello"), 
          L:==(L:Member(SomeOtherProperty, L:Var(entityY)), N:"Hello 2")),
     L:==(R:Member(Id, R:Var(entityX)), L:Member(Id, L:Var(entityY)))

如您所见,对于每个运算符,您的分析器必须根据其子节点的类别来决定类别。在上面的例子中:

  • 对对象进行属性访问将产生与对象相同的类别;
  • 字符串文字将是中性的;
  • 本地和远程子表达式的相等比较将具有本地类别;
  • and 运算符将再次偏爱本地而不是远程。

但是,您可以考虑将自下而上的方法与自上而下的优化传递相结合以获得更好的结果。考虑一下这个(象征性的):R == R + L。你想如何执行相等比较?使用纯自下而上的方法,您将在本地执行它。但是,在某些情况下,最好在本地预先计算 L,用实际值(即中性)替换子表达式并远程执行相等比较。换句话说,您最终可以实现一个查询计划优化器。

第 2 阶段:识别远程子表达式

在下一阶段,标记表达式树将自上而下进行处理,并将每个子表达式标记为远程从树中取出,并加入远程数据集中每个项目远程评估的表达式集。

从上面可以清楚地看出,某些远程子表达式将封装本地子表达式。因此,本地子表达式可能包含远程子表达式。只有中性节点才能表示与类别同质的子表达式。

因此,可能需要通过多次往返远程查询处理器来执行给定查询。另一种方法是允许查询处理器之间进行双向通信,以便“远程”处理器可以识别“本地”(从它的角度来看实际上是“远程”)子表达式并回调“本地”处理器执行它。

第 3 阶段:远程查询执行

在第三阶段,远程子表达式列表将被发送到“远程”查询处理器执行。 (另见上一阶段的讨论。)

问题还在于,如何识别可用于有效限制远程查询处理器返回的结果数据集的子表达式。为此,必须考虑表达式树中顶级运算符的语义(通常是&amp;&amp;||)。 &amp;&amp;|| 的短路评估使事情变得有点复杂,因为查询预处理器可能不会重新排序操作数。

第 4 阶段:本地查询执行

当所有远程子表达式都被执行时,它们在原始表达式树中的出现将被替换为收集的结果。

备注

  • 您最终可能需要限制仅允许在“远程”子树中允许的某些操作,以降低处理复杂性——这将是实现查询预处理器所花费的能力和时间之间的权衡.

  • 要处理数据别名(如在您提供的 PropX = entityX … i.PropX.SomeProperty == "Hello" 示例中),您必须执行数据流分析。在这里,您很可能会遇到一系列复杂到不值得处理的案例。

【讨论】:

  • 谢谢!这正是我所要求的
  • 很高兴听到这个消息!您是否打算开源解决方案?我很乐意关注您的进展并可能做出贡献。
  • 再次感谢,最重要的是,我只是想听听其他人会如何解决这个问题,看看它是否与我自己的想法完全不同。我没有考虑开源它,但考虑到让比我更聪明的人为它做出贡献的复杂性和潜在好处,我会认真考虑一下。
  • 好的,我很乐意继续通过电子邮件 (tucny(at)boldbrick(dot)com) 讨论它。但是,享受编码吧,它肯定会很有趣。
猜你喜欢
  • 1970-01-01
  • 2019-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-19
  • 1970-01-01
  • 2021-08-04
  • 2021-02-19
相关资源
最近更新 更多