LINQ学习笔记:创建更加复杂查询的策略
有3种策略可以应用于创建更加复杂的查询:
逐步创建 /使用into关键字/包装多个查询
逐步创建
之前我们曾演示过如何逐步的创建一个Lambda查询
1: string[] players = { "Tim Ducan", "Lebrom James", "Kobe Byrant" };
2: var filtered = players.Where (n => n.Contains ("a"));
3: var sorted = filtered.OrderBy (n => n);
4: var query = sorted.Select (n => n.ToUpper());
因为每一个参与的查询操作符都返回一个装饰序列, 其结果和你使用的单一的链式或多层装饰器的查询结果是一致的. 这里有几个潜在的好处, 如果你逐步的创建查询的话:
1. 查询更加简单容易编写
2. 可以有条件的添加查询操作符
在复合查询中使用逐步构造通常是很有用的做法. 为了演示, 假设我们想要使用正则表达式将所有的元音从一系列的名字中移除, 然后按字母排序并列出那些长度仍然大于2的名字. 使用Lambda语法, 我们可以使用一个单一的表达式完成:
1: string[] names = { "James","Jack","Landy","C.Y","Jay" };
2: IEnumerable<string> query = names
3: .Select (n => Regex.Replace (n, "[aeiou]", ""))
4: .Where (n => n.Length > 2)
5: .OrderBy (n => n);
6:
7: foreach(var name in query)
8: {
9: Console.WriteLine(name); //C.Y,Jck,Jms,Lndy
10: }
如果我们将它直接翻译成复合查询语法的话会碰到一些问题, 因为复合查询语句必须按照where-orderby-select的顺序出现编译器才能正确编译. 但是如果我们简单的按这个要求重新排列的话, 结果将会不一样:
1: string[] names = { "James","Jack","Landy","C.Y","Jay" }; IEnumerable<string> query =
2: from n in names
3: where n.Length > 2
4: orderby n
5: select Regex.Replace (n, "[aeiou]", "");
6:
7: foreach(var name in query)
8: {
9: Console.WriteLine(name); //C.Y,Jck,Jms,Jy,Lndy
10: }
幸运的是使用复合查询语法我们还可以有很多的方法可以得到跟上述查询一致的结果. 这其中的第一种就是使用逐步构造查询:
1: IEnumerable<string> query =
2: from n in names
3: select Regex.Replace (n, "[aeiou]", "");
4:
5: query = from n in query
6: where n.Length > 2
7: orderby n
8: select n;
9:
10: foreach(var name in query)
11: {
12: Console.WriteLine(name); //C.Y,Jck,Jms,Lndy
13: }
into关键字
into关键字可以让查询在经过一系列处理之后继续进行, 并且是逐步构造查询的快捷方式.使用into, 我们可以将上述的查询改写为:
1: IEnumerable<string> query =
2: from n in names
3: select Regex.Replace (n, "[aeiou]", "")
4: into noVowel
5: where noVowel.Length > 2
6: orderby noVowel
7: select noVowel;
唯一可以使用into的地方是在select或者group语句之后. Into”重新开始”一个新的查询, 并且允许你继续使用全新的where, orderby 和select语句.
作用域规则
所有的查询变量在into关键字之后都不再属于自己的作用范围, 例如下面的查询将不会被编译通过:
1: var query =
2: from n1 in names
3: select n1.ToUpper()
4: into n2
5: where n1.Contains ("x") // 非法, n1超出了作用域
6: select n2;
要了解为什么, 考虑一下将上述查询转换成Lamdba语法之后:
1: var query = names
2: .Select (n1 => n1.ToUpper())
3: .Where (n2 => n1.Contains ("x"));
当where运行的时候原始的名字(n1)已经丢失了, where操作符的输入序列只包含了大写的名字, 因此不能根据n1来过滤了.
包装查询
一个渐进查询可以被改造成一个包装查询, 如下例:
var tempQuery = tempQueryExpr
var finalQuery = from … in tempQuery…
也可以进一步改造为:
var finalQuery = from … in (tempQueryExpr)
包装查询从语义上等同于渐进式查询或者使用into关键字(没有中间变量). 最终结果就是只有一行包含多个操作符的查询语句. 例如, 考虑下面的查询:
1: IEnumerable<string> query =
2: from n in names
3: select Regex.Replace (n, "[aeiou]", "");
4:
5: query = from n in query
6: where n.Length < 2
7: orderby n
8: select n;
包装改造以后:
1: IEnumerable<string> query =
2: from n1 in
3: (
4: from n2 in names
5: select Regex.Replace (n2, "[aeiou]", "")
6: )
7: where n1.Length > 2 orderby n1 select n1;
当我们将它装成Lambda语法, 结果也是只有一行包含多个查询操作符的语句:
1: IEnumerable<string> query = names
2: .Select (n => Regex.Replace (n,"[aeiou]", ""))
3: .Where (n => n.Length > 2)
4: .OrderBy (n => n);
(编译器实际上不会执行最后的.Select(n=>n),因为它是多余的)
包装查询有时候可能会和我们之前谈过的子查询有点混淆, 因为它们之间很相似: 都有相似的inner和outer查询. 然而转换成Lambda语法的时候, 你会发现包装查询就是简单对于顺序链式操作符的包装(转换成Lambda之后操作符顺序跟包装查询的顺序完全一样). 而子查询并不是这样的, 它使用Lambda表达式将一个inner查询嵌入到另外一个当中. 待续!