LINQ之路 7:子查询、创建策略和数据转换

在前面的系列中,我们已经讨论了LINQ简单查询的大部分特性,了解了LINQ的支持计术和语法形式。至此,我们应该可以创建出大部分相对简单的LINQ查询。在本篇中,除了对前面的知识做个简单的总结,还会介绍几种创建更复杂查询的方式,让我们在面对更复杂的场景时也能轻松面对,包括:子查询、创建策略和数据转换。

子查询

在创建一个复杂的查询时,通常我们需要用到子查询。相信大家都记得SQL查询里的子查询,在创建LINQ查询时也是如此。在LINQ中,对于方法语法,一个子查询包含在另外一个查询的lambda表达式中,对于查询表达式语法来讲,所有不是from子句中引用的查询都是子查询。

下面的查询使用子查询来对last name进行排序,语句中的n.Split().Last()就是一个子查询:

            string[] names = { "David Tim", "Tony Sin", "Rager Witers" };            IEnumerable<string> query = names.OrderBy(n => n.Split().Last());

子查询的作用域限定在当前的lambda表达式中,并且可以引用外部lambda表达式的参数(查询表达式的范围变量)。

下面的查询获取所有长度最短的名字(注意:可能有多个):

        static void TestSubQuery()        {            string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };            // 获取所有长度最短的名字(注意:可能有多个)            IEnumerable<string> outQuery = names                .Where(n => n.Length == names     // 感谢A_明~坚持的指正,这里应该为==

                    .OrderBy(n2 => n2.Length)                    .Select(n2 => n2.Length).First());      // Tom, Jay"

            // 与上面方法语法等价的查询表达式            IEnumerable<string> outQuery2 =                from n in names                where n.Length ==            // 感谢A_明~坚持的指正,这里应该为==

                    (from n2 in names orderby n2.Length select n2.Length).First()                select n;

            // 我们可以使用Min查询运算符来简化            IEnumerable<string> outQuery2 =                from n in names                where n.Length == names.Min(n2 => n2.Length)                select n;        }

因为外部范围变量在子查询的作用域内,所以我们不能再次使用n作为内部查询的范围变量。

一个子查询在包含它的lambda表达式执行时被执行,这意味着子查询的执行取决于外部查询。需要注意的是:本地查询(LINQ to Objects)和解释查询(LIQN to SQL)对于子查询的处理方式是不一样的。对于本地查询,对于外部查询的每一次循环,子查询都会被重新执行一次。在稍后“解释查询”一篇中, 我们会看到,外部查询和子查询是作为一个单元进行处理的,这样,只需一次到远程数据源(如数据库)的连接。所以上面的例子对于一个数据库查询来说非常适合,但对于一个内存中的集合来说却效率低下,这时我们可以把子查询分离出来对让它只执行一次(这样它不再是一个子查询)。

            int shortest = names.Min(n => n.Length);            IEnumerable<string> query = from n in names                                        where n.Length == shortest                                        select n;

在延迟执行一篇中,我们说到元素和集合运算符如First和Count会让一个查询立即执行。但对一个子查询来说,即使是元素和集合运算符也不会改变外部查询延迟执行的特性。这是因为,不管是对本地查询还是通过表达式树访问的解释查询,子查询是间接调用的。

LINQ查询创建策略

通过前面几篇的讨论学习,我们已经了解了怎么去写一个比较简单的LINQ查询,也知道了创建LINQ查询的两种方式:方法语法和查询表达式。在这里,我们会描述三种创建复杂LINQ查询的创建策略:

渐进式创建查询

渐进式创建查询就是通过链接查询运算符的方式来创建LINQ查询。因为每一个查询运算符返回一个装饰者sequence,所以我们可以在其之上继续调用其它查询运算符。使用这种方式有如下几个优点:

  • 使得查询易于编写
  • 我们可以根据条件来决定是否调用某个查询运算符,如:if (includeFilter) query = query.Where(…)

渐进的方式通常是对查询的创建有益的,考虑如下的例子:我们需要在名字列表中去除所有名字的元音字母,然后对长度大于2的名字进行排序。在方法语法中,我们可以在一个表达式中完成这个查询:

            string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };            IEnumerable<string> query = names                .Select(n => n.Replace("a", "").Replace("e", "").Replace("i", "")                    .Replace("o", "").Replace("u", ""))                .Where(n => n.Length > 2)                .OrderBy(n => n);   // Result: Dck, Hrry, Mry

如果直接将上面的query改写成查询表达式语法,我们将会遇到麻烦,这时因为查询表达式要求要以Select或Group结束。但在上面的查询中,我们需要先做Select(结果投影)去除元音字母,再做过滤和排序。如果把Select直接放到后面,那么结果将会被改变。幸运的是,我们还是有办法让查询表达式来完成上面的工作,得到我们期望的结果。 第一种方式就是查询表达式的渐进式(分步)查询:

            IEnumerable<string> query =                from n in names                select n.Replace("a", "").Replace("e", "").Replace("i", "")                    .Replace("o", "").Replace("u", "");

            query = from n in query                    where n.Length > 2                    orderby n                    select n;   // Result: Dck, Hrry, Mry

into关键字

在我们前面查询表达式的例子中,select关键字的出现也就意味着查询的结束了。而into关键字让我们在结果投影之后还可以继续我们的查询,它是对分步构建查询表达式的一种简写方式。现在我们可以使用into关键字来重写上例中的查询:

            IEnumerable<string> query =                    from n in names                    select n.Replace("a", "").Replace("e", "").Replace("i", "")                            .Replace("o", "").Replace("u", "")                    into noVowel                    where noVowel.Length > 2                    orderby noVowel                    select noVowel;   // Result: Dck, Hrry, Mry

我们只能在select和group子句后面使用into关键字,它会重新开始一个查询,让我们可以继续引入where, orderby和select子句。尽管表面上看,我们重新创建了一个新的查询,但当上面的查询被翻译成方法语法时,它只是一个查询,一个链接了多个运算符的查询,所以上面的写法不会造成性能问题。

需要注意的是,所有的查询变量在into关键字之后都不再可见,下面的例子就说明了这一点:

            var query =                from n1 in names                select n1.ToUpper()                into n2                     //into之后只有n2可见                    where n1.Contains("x")  //Error: n1不可见                    select n2;

要理解其原因,我们只要看看它编译器为它翻译成对应的方法语法就能知晓:

            var query = names                .Select(n1 => n1.ToUpper())                .Where(n2 => n1.Contains("x")); //Error: n1不再可见,lambda表达式中只有n2

包装查询

渐进式查询创建方式可以通过在一个查询中嵌入另一个查询来改写,这样可以把多个查询组合成单个查询,即:

var tempQuery = tempQueryExpr

var finalQuery = from … in (tempQuery)

可以改写为:

var query = from … in (tempQueryExpr)

上面两种方式以及into关键字的工作方式是一样的,编译器都会把他们翻译成一个链接查询运算符。请看下面的示例:

            string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };

            // 渐进式查询(Progressive query building)            IEnumerable<string> query =                from n in names                select Regex.Replace(n, "[aeiou]", "");

            query = from n in query where n.Length > 2 orderby n select n;

            // 用包装查询方式进行改写(Wrapping Queries)            IEnumerable<string> query2 =                from n1 in                    (                        from n2 in names                        select Regex.Replace(n2, "[aeiou]", "")                    )                where n1.Length > 2                orderby n1                select n1;

            // 与上面等价的方法语法            IEnumerable<string> query3 = names                .Select(n => Regex.Replace(n, "[aeiou]", ""))                .Where(n => n.Length > 2)                .OrderBy(n => n);

数据转换

LINQ中的数据转换,也叫结果投影,是指LINQ查询select的输出。到目前为止,我们还只是看到了输出单个标量元素的示例。通过使用对象初始化器,我们可以输出更为复杂的结果类型。比如下面的示例,当我们在把姓名中的元音字母去掉之后,我还需要保存姓名的原始版本:

        class TempProjectionItem        {            public string Original;            public string Vowelless;        }

        static void TestProjectionStrategy()        {            string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };

            IEnumerable<TempProjectionItem> temp =                from n in names                select new TempProjectionItem                {                    Original = n,                    Vowelless = Regex.Replace(n, "[aeiou]", "")                };            //我们可以继续在结果中查询            IEnumerable<string> query =                from item in temp                where item.Vowelless.Length > 2 //按去除元音字母版本过滤                select item.Original;           //结果为姓名原始版本        }

匿名类型

上面我们自己定义了类型TempProjectionItem来存放查询的结果。通过使用匿名类型,我们可以省去这种中间类型的定义,而由编译器来帮我们完成:

            var intermediate = from n in names                               select new                               {                                   Original = n,                                   Vowelless = Regex.Replace(n, "[aeiou]", "")                               };            IEnumerable<string> query = from item in intermediate                                        where item.Vowelless.Length > 2                                        select item.Original;

需要注意的是,因为匿名类型的确切类型名是由编译器自动产生的,因此intermediate的类型为:IEnumerable <random-compiler-produced-name> 。我们来声明这种类型的唯一方式就是使用var关键字,这时,var不只是更加简洁,而且也是必需的手段。

let关键字

let关键字让我们可以在保持范围变量的同时引入新的查询变量。比如上面的示例,我们可以用let关键字作如下改写:

            string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };            var query = from n in names                        let Vowelless = Regex.Replace(n, "[aeiou]", "")                        where Vowelless.Length > 2                        select n;   //正是因为使用了let,此时n仍然可见

let关键字非常灵活和方便,就像例子看到的那样。而且,我们可以使用多个let关键字,并且后面的 let表达式可以引用前一个let关键字引入的变量。

本系列LINQ之路文章到此已经对LINQ to Objects进行了比较详细的讨论,接下去的打算是对解释查询(LINQ to SQL, LINQ to XML等)以及更多的查询运算符进行讨论和学习。希望系列文章能对阅者有些帮助,也期待大家的意见和提议^_^。

时间: 2024-11-03 21:09:25

LINQ之路 7:子查询、创建策略和数据转换的相关文章

实战 EF(LINQ) 如何以子查询的形式来 Join

如题,大多数网上关于 LINQ Join 的示例都是以 from x in TableA  join ... 这样的形式,这种有好处,也有劣势,就是在比如我们使用的框架如果已经封装了很多方法,比如分页方法.而我们的业务方法只需要在 Service 层调用框架的分页方法,同时注入条件拼接的委托就可以了.而这时候,为了简单,就会以调用 Join() 方法来实现关联查询,外部看起来好像是子查询,而实际上 Entity Framework 生成 SQL 时,还是会以 Inner join 的形式来生成

使用子查询创建表(oracle)

转自:https://blog.csdn.net/lxh123456789asd/article/details/81164321 语句: CREATE TABLE tablename[(column1,column2,column3.....] AS subquery; 例子: 1.create table tabletest as select * from emp where 1=2; 这句话后面的where条件为假,表示只复制表的结构,而不复制数据,当where表后面的条件为真的时候,表

LINQ之路 5:LINQ查询表达式

书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询表达式(Query Expression). LINQ方法语法的本质是通过扩展方法和Lambda表达式来创建查询.C# 3.0对于LINQ表达式还引入了声明式的查询表达式,也叫查询语法,通常来讲,它是创建LINQ查询的更加快捷的方式.尽管通过查询语法写出的查询比较类似于SQL查询,但实际上查询表达式的产生并不是建立在SQL之上,而是建立在函数式编程语言如LISP和Haskell中的list comprehensio

SQL随着子查询结果更新多个字段

笔者:iamlasong 要求:表格内容需要改变,在临时表中内容的变化,使用SQL官方声明更新表若干领域. 假设更新一个字段,直接用字段名=子查询就能够了,多个字段更新,将字段在括号里并列写出就可以,例如以下: update tb_jg t set t.jgfl = 'sd', (     t.zj_code, t.zj_mc) = (select a.zj_code, a.zj_mc from song_temp a where a.zj_code = t.zj_code) where exi

Mysql子查询,连接,多表更新

1.子查询是指在另一个查询语句中的SELECT子句. 例句: SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2); 其中,SELECT * FROM t1 ...称为Outer Query[外查询](或者Outer Statement), SELECT column1 FROM t2 称为Sub Query[子查询]. 所以,我们说子查询是嵌套在外查询内部.而事实上它有可能在子查询内部再嵌套子查询. 子查询必须出现在圆括号之间. 行

MySQl 子查询,左右连接,多表连接学习笔记

1.子查询是指在另一个查询语句中的SELECT子句. 例句: SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2); 其中,SELECT * FROM t1 ...称为Outer Query[外查询](或者Outer Statement), SELECT column1 FROM t2 称为Sub Query[子查询]. 所以,我们说子查询是嵌套在外查询内部.而事实上它有可能在子查询内部再嵌套子查询. 子查询必须出现在圆括号之间. 行

SQL用子查询结果更新多个字段

作者:iamlasong 要求:表格的内容需要变更,变更的内容放在一个临时表中,用SQL语句更新正式表中多个字段. 如果更新一个字段,直接用字段名=子查询就可以了,多个字段更新,将字段在括号中并列写出即可,如下: update tb_jg t set t.jgfl = 'sd', (     t.zj_code, t.zj_mc) = (select a.zj_code, a.zj_mc from song_temp a where a.zj_code = t.zj_code) where ex

Oracle基本语法&amp;&amp;函数&amp;&amp;子查询&amp;&amp;分页查询&amp;&amp;排序&amp;&amp;集合操作&amp;&amp;高级分组函数

一.  数据库 手工---文件管理---数据库 DB:Database 数据库. DBMS:管理数据库的软件.(oracle) 主流关系数据库: 1.      Oracle 2.      DB2 3.      SQL Server 基本没人使 4.      MySQL  基本没人用,免费 Linux 开源,可以发现漏洞补上 Windows服务器会有补丁,数据易泄漏 eclipse 日食 数据表(Table): 表的行(Row):记录 表的列(Column):字段 二.  关系型数据库 一

mysql学习笔记之连接查询与子查询

mysql连接查询与子查询 1.子查询是指在另一个查询语句中的SELECT子句. 例句: SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2); 其中,SELECT * FROM t1 ...称为Outer Query[外查询](或者Outer Statement), SELECT column1 FROM t2 称为Sub Query[子查询]. 所以,我们说子查询是嵌套在外查询内部.而事实上它有可能在子查询内部再嵌套子查询. 子查