表达式树依赖性分析器

Jef*_*eff 6 c# linq lambda expression-trees

我正在为交叉数据源IQueryProvider构建表达式树依赖性分析器.

也就是说,我有一个IQueryable,其中包含一些可以在内存中针对某个任意提供程序(比如Entity Framework)执行的元素.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();
Run Code Online (Sandbox Code Playgroud)

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

  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();
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢你的帮助.

Ond*_*cny 4

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

\n\n
    \n
  1. 表达式树的表达式分析(可能是自下而上),并将节点标记为 \xe2\x80\x9cremote\xe2\x80\x9d、\xe2\x80\x9clocal\xe2\x80\x9d 和 \xe2\x80\x9cneutral \xe2\x80\x9d
  2. \n
  3. 自上而下识别 \xe2\x80\x9cremote\xe2\x80\x9d 子表达式
  4. \n
  5. 远程查询执行(子表达式消除)
  6. \n
  7. 本地查询执行
  8. \n
\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n
    \n
  • \xe2\x80\x9cremote\xe2\x80\x9d 节点对应必须远程执行的操作;
  • \n
  • \xe2\x80\x9clocal\xe2\x80\x9d 节点对应必须在本地执行的操作;
  • \n
  • \xe2\x80\x9cneutral\xe2\x80\x9d 节点对应于可由任何查询处理器执行的操作。
  • \n
\n\n

用于遍历和处理表达式树的自下而上的方法似乎适合这种情况。原因是当处理给定节点X时,具有子节点Y_1至Y_n,节点X的类别严重依赖于其子节点Y_1至T_n的类别。

\n\n

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

\n\n
entityX.SomeProperty == "Hello" &&\nentityY.SomeOtherProperty == "Hello 2" && \nentityX.Id == entityY.Id\n
Run Code Online (Sandbox Code Playgroud)\n\n

进入相应表达式树的轮廓:

\n\n
&&(&&(==(Member(SomeProperty, Var(entityX)), "Hello"), \n      ==(Member(SomeOtherProperty, Var(entityY)), "Hello 2")),\n   ==(Member(Id, Var(entityX)), Member(Id, Var(entityY)))\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后,该表达式树将被自下而上标记。R对于 \xe2\x80\x9cremote\xe2\x80\x9d,L对于 \xe2\x80\x9clocal\xe2\x80\x9d,N对于 \xe2\x80\x9cneutral\xe2\x80\x9d。提供entityX远程和entityY本地结果将如下所示:

\n\n
L:&&(L:&&(R:==(R:Member(SomeProperty, R:Var(entityX)), N:"Hello"), \n          L:==(L:Member(SomeOtherProperty, L:Var(entityY)), N:"Hello 2")),\n     L:==(R:Member(Id, R:Var(entityX)), L:Member(Id, L:Var(entityY)))\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
    \n
  • 对对象进行属性访问将产生与该对象相同的类别;
  • \n
  • 字符串文字将是中性的;
  • \n
  • 本地和远程子表达式的相等比较将具有本地类别;
  • \n
  • and 操作员将再次青睐本地而不是远程。
  • \n
\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n

第三阶段:远程查询执行

\n\n

在第三阶段,远程子表达式列表将被发送到 \xe2\x80\x9cremote\xe2\x80\x9d 查询处理器来执行。(另请参阅上一阶段的讨论。)

\n\n

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

\n\n

第 4 阶段:本地查询执行

\n\n

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

\n\n

评论

\n\n
    \n
  • 您最终可能需要限制 \xe2\x80\x9cremote\xe2\x80\x9d 子树中仅允许某些操作,以降低处理复杂性 \xe2\x80\x94 这将是功能和时间之间的权衡花费在实现查询预处理器上。

  • \n
  • 要处理数据别名(如PropX = entityX \xe2\x80\xa6 i.PropX.SomeProperty == "Hello"您提供的示例中所示),您必须执行数据流分析。在这里,您很可能会遇到一组过于复杂而不值得处理的案例。

  • \n
\n