BlogEngine.Net架构与源代码分析系列part9:开发扩展(上)
如果我们使用Select的话也可以使用如下的方法得到一样的结果: 1: IEnumerable<string[]> query = 2:
3: fullNames.Select (
4:
5: name => name.Split( ));
6:
7: foreach (string[] stringArray in query) 8:
9: foreach (string name in stringArray) 10:
11: Console.Write (name + “,”);
SelectMany的好处就是它生成了单一扁平的结果序列. 通过一个额外的生成符——额外的from语句可以让复合语法支持SelectMany.这个from关键字拥有两层意思,在查询开始的那一个引出了迭代变量和输入序列,查询中其他地方出现的from关键字将被翻译成SelectMany: 1: IEnumerable<string> query = 2:
3: from fullName in fullNames 4:
5: from name in fullName.Split() 6:
7: select name;
注意,第二个from引出了一个新的查询变量——这本例中,是name.从那时起,这个新的变量将会变成迭代变量,而原始的迭代变量将会变成外部迭代变量.
外部迭代变量 在我们前面的例子中,fullName在SelectMany之后变成了一个外部迭代变量. 这个外部迭代变量依然在可用范围内直到查询结束或者到达一个into从句: 1: IEnumerable<string> query = 2:
3: from fullName in fullNames // 外部变量 4:
5: from name in fullName.Split( ) // 迭代变量 6:
7: select name + ” came from “ + fullName;
8:
9: Anne came from Anne Williams
10:
11: Williams came from Anne Williams
12:
13: John came from John Fred Smith
如果要使用Lambda语法的话,尤其在有where或者orderby的情况下,会更加复杂一点,这里有一个技巧: 1: from fullName in fullNames 2:
3: from x in 4:
5: fullName.Split( )
6:
7: .Select (name => new { name, fullName } ) 8:
9: orderby x.fullName, x.name
10:
11: select x.name + ” came from “ + x.fullName;
我们使用了一个匿名类型来搭载临时数据,类似let从句的作用.最后我们可以得到一个完整的Lambda语法的查询: 1: IEnumerable<string> query = fullNames 2:
3: .SelectMany (fName =>
4:
5: fName.Split( )
6:
7: .Select (name => new { name, fName } )) 8:
9: .OrderBy (x => x.fName)
10:
11: .ThenBy (x => x.name)
12:
13: .Select (x => x.name + ” came from “ + x.fName);
复合查询的思考 正如我们前面看到的例子,如果我们需要外部迭代变量, 那么使用复合语法是一个很好的选择,因为它的语义让你可以更加直观的思考. 编写额外的生成器主要有两个基础的模式, 第一个是扩展和平整结果,为了做到这一点,我们可以通过调用已有查询变量的属性或者方法来完成,上面的例子我们就使用了这个方法. 在LINQ to SQL中一个类似的查询就是当你扩展关联的子属性,以下的实例列出了所有的客户和他们对应的采购单: 1: IEnumerable<string> query = 2:
3: from c in dataContext.Customers 4:
5: from p in c.Purchases 6:
7: select c.Name + ” bought a “ + p.Description;
8:
9: Tom bought a Bike
10:
11: Tom bought a Holiday
12:
13: Dick bought a Phone
14:
15: Harry bought a Car
另外一个模式就是执行一个交叉连接,为了做到这一点, 我们的selector表达式返回的将会是一个序列而不是一个迭代变量了: 1: int[] numbers = { 1, 2, 3 }; 2:
3: string[] letters = { “a”, “b” }; 4:
5: IEnumerable<string> query = from n in numbers 6:
7: from l in letters 8:
9: select n.ToString( ) + l;
10:
11: RESULT: { “1a”, “1b”, “2a”, “2b”, “3a”, “3b” }
这种风格的查询就是SelectMany风格关联的基础. 使用SelectMany做关联 我们可以使用SelectMany,并通过过滤一个交叉结果集来关联两个序列.例如, 假设我们要安排一场比赛的选手对决, 我们使用以下的查询: 1: string[] players = { “Tom”, “Jay”, “Mary” }; 2:
3: IEnumerable<string> query = 4:
5: from name1 in players 6:
7: from name2 in players 8:
9: select name1 + ” vs “ + name2;
10:
11: RESULT: {“Tom vs Tom”, “Tom vs Jay”, “Tom vs Mary”,
12:
13: “Jay vs Tom”, “Jay vs Jay”, “Jay vs Mary”,
14:
15: “Mary vs Tom”, “Mary vs Jay”, “Mary vs Mary”}
但这并非我们期望的结果,很明显,选手自己并不需要与自己比赛,因此我们必须要过滤这些不符合条件的输出元素: 1: IEnumerable<string> query = 2:
3: from name1 in players 4:
5: from name2 in players 6:
7: where name1.CompareTo (name2) > 0 8:
9: orderby name1, name2
10:
11: select name1 + ” vs “ + name2;
12:
13: RESULT: { “Jay vs Mary”, “Jay vs Tom”, “Mary vs Tom” }
此过滤断言构成了连接条件,我们的查询可以被称为非等连接,因为连接条件没有使用等式操作符.
LINQ to SQL当中的SelectMany LINQ to SQL当中的SelectMany可以被用于执行交叉连接,非等连接,inner join以及left outer join.我们可以像使用Select一样使用SelectMany,不同的地方在于SelectMany返回的是一个扁平的数据结构而不是层级机构. 交叉连接就像我们上面例举的类似,以下的查询显示了每一个客户对应的每一个采购: 1: var query =
2:
3: from c in dataContext.Customers 4:
5: from p in dataContext.Purchases 6:
7: select c.Name + ” might have bought “ + p.Description;
更通常的情况是我们只想列出客户及其对应的采购单,为此,我们可以增加一个where语句和一个连接断言,结果将会得到一个类似SQL语法的查询: 1: var query =
2:
3: from c in dataContext.Customers 4:
5: from p in dataContext.Purchases 6:
7: where c.ID == p.CustomerID 8:
9: select c.Name + ” bought a “ + p.Description;
如果在你的LINQ to SQL实体类中存在关联属性,你可以使用它们来避免交叉连接: 1: from c in dataContext.Customers 2:
3: from p in c.Purchases 4:
5: select new { c.Name, p.Description };
这样写的好处是我们消除了关联断言,但结果是一致的. 我们还可以增加一个Where语句来做进一步的过滤,例如,我们只想列出那些名字以J开头的客户: 1: from c in dataContext.Customers 2:
3: where c.Name.StartsWith (“J”) 4:
5: from p in c.Purchases 6:
7: select new { c.Name, p.Description };
你也可以将where语句往下移一行,对于这个LINQ to SQL查询,结果是一致的.然后,如果是本地查询的话,这会得到一个相对差一些的性能.对于本地查询,我们总是应该使用before joining. 我们还可以通过增加from语句引出新的表,例如,每一个采购单都有采购明细: 1: from c in dataContext.Customers 2:
3: from p in c.Purchases 4:
5: from pi in p.PurchaseItems 6:
7: select new { c.Name, p.Description, pi.DetailLine };
另外,如果通过关联属性从父表开始选择数据的话我们不需要添加from语句. 1: from c in dataContext.Customers 2:
3: select new { 4:
5: Name = c.Name,
6:
7: SalesPerson = c.SalesPerson.Name
8:
9: };
在这个例子中,我们并没有使用SelectMany因为没有子集合需要处理.关联属性返回了一个单一的项.
SelectMany和Outer join 在前面使用select的left outer join例子中: 1: from c in dataContext.Customers 2:
3: select new { 4:
5: c.Name,
6:
7: Purchases =
8:
9: from p in c.Purchases 10:
11: where p.Price > 1000 12:
13: select new { p.Description, p.Price } 14:
15: };
每一个客户都会被包括,不管它是否包含任何订单. 假设我们现在要使用SelectMany重新编写这个查询: 1: from c in dataContext.Customers 2:
3: from p in c.Purchases 4:
5: where p.Price > 1000 6:
7: select new { c.Name, p.Description, p.Price };
然而这个查询得到的是一个inner join的效果,只有包含Price>1000的采购单的客户才会被包括进来. 为了能够得到一个left outer join结果,我们可以在内部序列上使用DefaultIfEmpty查询操作符,此方法对于没有包含任何元素的输入序列返回null: 1: from c in dataContext.Customers 2:
3: from p in c.Purchases.DefaultIfEmpty() 4:
5: select new { 6:
7: c.Name,
8:
9: p.Description,
10:
11: Price = (decimal?) p.Price 12:
13: };
这个查询在LINQ to SQL当中工作良好,并且将会返回所有的客户即便他们不包含任何的采购单. 但对于本次查询,当p等于null的时候将会得到一个NullReferenceException异常, 不过我们还是可以进一步完善它: 1: from c in dataContext.Customers 2:
3: from p in c.Purchases.DefaultIfEmpty( ) 4:
5: select new 6:
7: {
8:
9: c.Name,
10:
11: Descript = p == null ? null : p.Description, 12:
13: Price = p == null ? (decimal?) null : p.Price 14:
15: };
接下来让我们引出Price过滤器,我们不能简单的像以前做的那样直接加上where语句,因为它会在DefaultIfEmpty之后执行,正确的解决方案是我们需要一个子查询并且必须让Where语句在DefaultIfEmpty之前: 1: from c in dataContext.Customers 2:
3: from p in c.Purchases.Where (p => p.Price > 1000) 4:
5: .DefaultIfEmpty( )
6:
7: select new 8:
9: {
10:
11: c.Name,
12:
13: Descript = p == null ? null : p.Description, 14:
15: Price = p == null ? (decimal?) null : p.Price 16:
17: };
这个查询在LINQ to SQl当中将会被很好的转换成left outer join, 并且对于编写这类型的查询这是一个比较有效率的做法. 待续! |
凌众科技专业提供服务器租用、服务器托管、企业邮局、虚拟主机等服务,公司网站:http://www.lingzhong.cn 为了给广大客户了解更多的技术信息,本技术文章收集来源于网络,凌众科技尊重文章作者的版权,如果有涉及你的版权有必要删除你的文章,请和我们联系。以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢! |