「译」LINQ: Building an IQueryable Provider - Part VI - Nested queries

英文原文是Matt Warren发表在MSDN Blogs的系列文章之一,英文渣渣,翻译不供参考,请直接看原文

你又以为这个系列已经完成,所以我已经转移到其他阵地上去了吗?因为Select操作工作得非常好,所以你以为前面所讲的就是你构建自己的IQueryable提供程序所需要了解的所有内容了吗?哈!还有很多需要学习的呢,而且,Select操作还是有些漏洞。

Finishing Select

有漏洞?怎么可能?我把你当成从来不会出错的微软大神,但是你却说你给我的是劣质的代码?我把已经把代码复制粘贴到产品里,老板已经说了下周一就启动!你怎么能这么做?(喘气)

放心啦,不是什么严重的漏洞,只是一点小小的缺陷而已。

回想一下,在上篇文章中,我建了四种表达式节点,Table,Column,Select和Projection,它们工作十分良好,不是吗?有漏洞的地方是我没有考虑到所有可以写查询表达式的地方。我考虑到的只是最明显的Projection节点出现在查询表达式树顶的情况。毕竟,因为我只支持SelectWhere,所以最后一个操作必定是这两者之一。我的代码就是这样假设的。

这不是问题所在。

问题是Projection节点也有可能出现在选择器表达式里面,例如,看下面的查询。

1
2
3
4
5
6
7
var query = from c in db.Customers
select new {
Name = c.ContactName,
Orders = from o in db.Orders
where o.CustomerID == c.CustomerID
select o
};

我在选择器表达式里面写了一个嵌套查询,这与我们之前写的表格式的查询非常不一样。现在我希望我们的提供程序创建嵌套的对象,每个对象都有一个名字和一个订单的集合。这样的查询要怎么实现?SQL甚至都做不到这一点。即使我彻底不支持这种写法,万一有人真的这么写又会发生什么呢?

额,抛出了一个异常,然而并不是我预想的那个异常,看来代码中的bug比我预想的要多。因为这个可爱的查询在选择器表达式中有一个ProjectionExpression,所以我期望在编译投影器函数的时候会抛出一个异常。我之前说过添加自己的表达式节点是没问题的对吧?理由是只有我们才能看到这些节点,哈,看来是我错了。(实际上抛出来的异常是因为我在构建Projection节点的时候弄错了它们的类型而导致的,这个以后再修复。)

现在假设我已经修复了这个类型异常,我要如何处理这个嵌套的Projection节点呢?我可以捕捉这个异常,然后抛出一个自己的异常,加个道歉声明说不支持嵌套查询。但是这样的话我就不是一个好的LINQ开发者,也享受不到解决这个问题的乐趣了。

所以,让我们继续前进吧。

Nested Queries

我希望能够将嵌套的ProjectionExpression转换为嵌套的查询。SQL实际上也做不到这一点,所以我必须在自己的代码做一些事情以达到这种效果。然而,在这里我并不打算做成一个超级完善的解决方案,我只要能取回数据就够了。

因为投影器函数必须要转换为可执行的代码,所以我得将里面的ProjectionExpression节点给替换成从某个地方获取数据以构建Orders集合的代码。数据不可能来自现有的DataReader,因为它只能保存表格式的结果,因此应该来自另一个DataReader。我真正要做的就是将ProjectionExpression转换成执行的时候返回这个集合的一个函数。

我们好像在之前见过类似的东西?

思考中。。。

对,这或多或少就是我们的提供程序所做的事情。呼,事情好像有点难。提供程序早已通过Execute方法将表达式树转换成了结果序列。我想我已经完成一半了。

所以我需要在之前的ProjectionRow类中添加一个执行嵌套查询的函数,它回调提供程序以执行真正的工作。

下面是ProjectionRowProjectionBuilder的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public abstract class ProjectionRow {
public abstract object GetValue(