使用查询表达式的LINQ
 
本章介绍了一种新的语法,查询表达式。
 
1、查询表达式概述
2、特点:投射  筛选  排序   Let  分组
3、作为方法调用
 
标准查询运算符所实现的查询在功能上与SQL中实现的查询非常相似
 
C#3.0中添加了一个新的语法:查询表达式。
 
本章将介绍新的查询表达式语法,并利用这个语法对上一章的许多查询进行表示。
 
一、查询表达式概述
除了遍历集合中的所有项之外,开发者经常要执行的另一个操作是对集合进行筛选,目的是
最终只需要遍历数量较少的项,或者对集合进行投射,使其中的项变成另一个形式。
如:
有一个文件集合,可以在垂直方向上筛选它,使结果集合仅包含.cs文件,或者仅包含10个最大的文件。
除此之外,还可以在“水平”方向对文件集合进行“投射”,使新集合只包含两项数据:文件的目录路径以及目录的大小。
 
查询表达式一般以一个"from子句"开始,以及一个"select子句"或者"groupby子句"结束。
每个子句都分别用上下文关键字from、select或group来标识。

             string[] KeyWords = { "", "c*d", "xxm","2*","ab" };

             IEnumerable<string> selection = from word in KeyWords
where !word.Contains('*')
select word; foreach (string word in selection)
{
Console.WriteLine(word);
}
 
在这个查询表达式中,我们将一个C#关键字集合赋给selection,集合中排除了上下文关键字(含有*的关键字)
 
C#查询表达式以上下文关键字from开始。
之所以要采用这个设计,目的是为了方便实现智能感知功能。
如以上的代码:由于最开始出现的是from,并将字符串数组KeyWords指定为数据源,所以代码编辑器知道wrod是string类型。
这样一来,IDE的智能感知功能就可以立即发挥作用----在word之后输入一个成员访问运算符,将只显示出string的成员。
相反,如果from子句出现在select之后,那么from子句之前的任何加点运算符都无法确定word的数据类型是什么。
所以就无法显式word的成员列表。
这里的wrod称为范围变量,它代表集合中的每一项。
 
1、投射
查询表达式输出的是一个IEnumerable<T>或IQuerable<T>集合。T的数据类型是从select或groupby子句推导的。
 
用表达式来一个特定类型的集合时,结果并非一定是原始类型。
而select子句允许将数据投射成一个完全不同的类型。

             IEnumerable<FileInfo> files = from fileName in Directory.GetFiles("D:\\")
select new FileInfo(fileName); foreach (FileInfo file in files)
{
Console.WriteLine("{0}({1})", file.Name, file.LastWriteTime);
}
输出:
bssndrpt.lg(2014/10/8 23:06:06)
cache_index.db(2015/1/18 18:13:10)
league of legends.lg(2014/11/30 21:05:52)
QQ图片20140919173746.jpg(2014/9/19 17:37:50)
 
注:
本身fileName是一个string类型,但是经过处理,返回的是FileInfo类型。
最后返回的是一个IEnumerable<FileInfo>,而不是IEnumerable<string>数据类型。
 
事实上,C#3.0之所以要引入匿名类型,在很大程序上就是为了利用像这样的"投射"功能。
通过匿名类型,可以在不定义一个显式类型的前提下选择符合自己要求的数据。
 
             var files = from fileName in Directory.GetFiles("D:\\")
select new
{
Name = fileName,
LastWriteTime = new FileInfo(fileName).LastAccessTime
}; foreach (var file in files)
{
Console.WriteLine("{0}({1})", file.Name, file.LastWriteTime);
}
假如数据量非常大,而且检索这些数据的代价非常高,那么像这样在"水平"方向上进行投射,
从而减少与集合中的每一项关联的的数据量,就可以发挥非常积极的作用。
执行一个查询时,不是获取全部数据,而是通过匿名类型,只在集合中存储或获取需要的数据。
如果没有匿名类型,开发人员要么使用含有不需要的信息的对象,要么定义一些小的、特定的类,
这些类只用于存储需要的特定数据。
而匿名类型允许由编译器(动态)定义类型。在这种类型中,只包含当前情况所需要的数据。
在其他情况下,则可以采用不同的方式进行投射。
 
"推迟执行"同样适用于查询表达式。
赋值本身不会执行查询表达式。
from子句会在赋值时执行,但是,除非代码开始遍历selection的值,否则,无论投射、筛选,还是from子句之后的一切都不会执行。
 
selection同时扮演了查询和集合两个角色。
更多,待查。
 
2、筛选
 
使用where 子句在"垂直"方向上筛选集合,结果集合中将包含较少的项(每个数据项都相当于数据表中的一行记录)。
筛选条件(filter criteria0是用一个断言来表示的。
所谓断言,本质上就是返回布尔值的一个Lambda表达式。比如word.Contains()

            var files = from fileName in Directory.GetFiles("D:\\")
where File.GetLastWriteTime(fileName).Year<
select new
{
Name = fileName,
LastWriteTime = new FileInfo(fileName).LastAccessTime
}; foreach (var file in files)
{
Console.WriteLine("{0}({1})", file.Name, file.LastWriteTime);
}
 
3、排序
在查询表达式中对数据项进行排序的是orderby子句。

             var files = from fileName in Directory.GetFiles("D:\\")
where File.GetLastWriteTime(fileName).Year<
orderby (new FileInfo(fileName)).Length descending,fileName
select new
{
Name = fileName,
LastWriteTime = new FileInfo(fileName).LastAccessTime
}; foreach (var file in files)
{
Console.WriteLine("{0}({1})", file.Name, file.LastWriteTime);
}
 
首先按文件长度降序排序,然后按文件名升序排序。多个排序条件以逗号分隔。
 
4、let子句
上一个问题大于,为了求值文件升序,在orderby子句和select子句中都要有FileInfo的一个实例。

             var files = from fileName in Directory.GetFiles("D:\\")
where File.GetLastWriteTime(fileName).Year <
let file = new FileInfo(fileName)
orderby file.Length descending, fileName
select new
{
Name = fileName,
LastWriteTime = file.LastAccessTime
};
用let子句添加的表达式可以在整个查询表达式的范围内使用,为了添加第二个let表达式,只需把它作为一个附加的子句,
放在第一个from之后和最后一个select/groupby子句之前便可。无需要任何分隔符来分隔表达式。
 
5、分组
 
在SQL中,这通常涉及将数据项聚合成一个汇总的header或total---称为一个聚合值。
然而,C#的表达力更强一些。
除了提供与每个分组有关的聚合信息,查询表达式还允许组内单独的项构成一系列子集合,父
列表中的每个项都对应这样的一个子集合。

             string[] KeyWords = { "", "c*d", "xxm", "2*", "ab" };

             IEnumerable<IGrouping<bool, string>> selection =
from word in KeyWords
group word by word.Contains('*'); //分成了两组,每一组都是一个 IGrouping<bool, string> 类型
foreach (IGrouping<bool, string> wordGroup in selection)
{
Console.WriteLine(Environment.NewLine + "{0}", wordGroup.Key ? "关键字" : "非关键字"); foreach (string word in wordGroup)
{
Console.WriteLine(word);
}
}
输出:
 
非关键字
12
xxm
ab
 
关键字
c*d
2*
 
 
 
首先,列表中的每一项都是IGrouping<bool,string>类型。
IGrouping<TKey,TElement>的类型是由group和by后面的数据类型分别决定的。
也就是说TElement之所以是string,是因为word是一个string。TKey的类型则是由by后面的
数据类型来决定的。在本例中,word.Contains()返回一个Boolean,所以TKey是一个bool。
 
group by子句的第二个特点在于,它允许我们使用一个嵌套的foreach循环,以便遍历前面提到的集合。
如上例,行打印关键字的类型作为标题,再循环打印出其中分组中的每个关键字。
 
第三,可以在group by子句的末尾附加一个select子句,从而实现"投射"功能。
从广义上说,select子句是通过"查询延续"机制来附加的---任何查询主体都可以附加到其他查询主体
的后面,这称为"查询延续"(query continuation)。
 

             string[] KeyWords = { "", "c*d", "xxm", "2*", "ab" };

             IEnumerable<IGrouping<bool, string>> keywordGroups =
from word in KeyWords
group word by word.Contains('*'); var selection = from groups in keywordGroups
select new
{
IsContextualKeyword = groups.Key,
Items = groups
}; //分成了两组,每一组都是一个
//{
// IsContextualKeyword = groups.Key,
// Items = groups
//};类型
foreach (var wordGroup in selection)
{
Console.WriteLine(Environment.NewLine + "{0}", wordGroup.IsContextualKeyword ? "关键字" : "非关键字"); foreach (string word in wordGroup.Items)
{
Console.WriteLine(word);
}
}
 
输出:
 
非关键字
12
xxm
ab
 
关键字
c*d
2*
 
 
group by 子返回由IGrouping<TKey,TElement>对象构成的一个集合---这和14章讲过的标准查询运算符GroupBy()是一样的。
 
 
6、使用into进行查询延续
 
group by 查询后面是第二个从分组中投射出一个匿名类型的查询。
此时不是写一个额外的查询,而是直接使用上下文关键字into,通过一个查询延
续子句来扩展查询。
into允许用一个范围变量来命名group by子句返回的每个数据项。
into子句是作为附加查询命令的一个生成器来使用的。

             string[] KeyWords = { "", "c*d", "xxm", "2*", "ab" };

             var selection =
from word in KeyWords
group word by word.Contains('*')
into groups//范围变量,代表group by子句返回的每个数据项
select new
{
IsContextualKeyword = groups.Key,
Items = groups
}; //分成了两组,每一组都是一个
//{
// IsContextualKeyword = groups.Key,
// Items = groups
//};类型
foreach (var wordGroup in selection)
{
Console.WriteLine(Environment.NewLine + "{0}", wordGroup.IsContextualKeyword ? "关键字" : "非关键字"); foreach (string word in wordGroup.Items)
{
Console.WriteLine(word);
}
}
使用into 在现有查询结果上运行附加的查询,这不是group by子句特有的一个功能,
而是所有查询表达式的一个功能。查询延续提供了一种简化的编程手段,它替代了写多个单独
的查询表达式的形式。
into避免了利用第一个查询的结果来写第二个查询,它可作为一个管道运算符使用,将第一个查询的结果同
第二个查询的结果合并到一起。
 
不重复的成员:
查询表达式没有专门为不重复的成员规定一个语法,但是可以通过查询运算符Distinct()来实现。
             var selection = (
from word in KeyWords
select word
).Distinct();
 
查询表达式的编译:
查询表达式其实是对底层API的一系列方法调用。
如where 表达式会转换成对System.Linq.Enumerable的 Where()扩展方法的调用。
where子句指定的条件就像14章描述的那样,成为由Where() 方法指定的条件。
 
实现隐式执行:
将选择条件保存到selection中,而不在赋值的时候就执行查询,这个功能是通过委托来实现的。
编译器将查询表达式转换成在目标上调用的方法,它获取委托作为参数。
委托是一种特殊的对象,它保存的信息规定了在委托被调用时要执行什么代码。
由于委托只包含与要执行的东西有关的数据,所以可以保存下来,直到以后执行时才拿出来使用。
 
如果集合实现了IQueryable<T>(LINQ providers),Lambda表达式会转换成表达式树。
表达式树是一种层次化的数据结构,它递归地分解成子表达式。
表达式树通常可以被枚举,然后在另一种语言(比如SQL)中重建为原始的表达式树。
 
二、查询表达式作为方法调用
 
CLR和IL并不需要对查询表达式进行任何实现。相反,是由C#编译器将查询表达式转换成方法调用。
 
扩展方法和Lambda表达式的组合,构成了查询表达式提供的功能的一个超集。
所有查询表达式都能转换成方法调用。而方法表达式并非问题能转换成查询表达式。
平时应该尽可能地使用查询表达式,但偶尔也要依赖于方法调用。
无论如何,一个复杂的查询通常都有必要分解成多个语句,甚至分解成多个方法。
 

十五、C# 使用查询表达式的LINQ的更多相关文章

  1. C#复习笔记(4)--C#3:革新写代码的方式(查询表达式和LINQ to object(下))

    查询表达式和LINQ to object(下) 接下来我们要研究的大部分都会涉及到透明标识符 let子句和透明标识符 let子句不过是引入了一个新的范围变量.他的值是基于其他范围变量的.let 标识符 ...

  2. 《C#本质论》读书笔记(15)使用查询表达式的LINQ

    15.1 查询表达式的概念 简单的查询表达式 private static void ShowContextualKeywords1() { IEnumerable<string> sel ...

  3. C#学习笔记五: C#3.0Lambda表达式及Linq解析

    最早使用到Lambda表达式是因为一个需求:如果一个数组是:int[] s = new int[]{1,3,5,9,14,16,22};例如只想要这个数组中小于15的元素然后重新组装成一个数组或者直接 ...

  4. 查询表达式和LINQ to Objects

    查询表达式实际上是由编译器“预处理”为“普通”的C#代码,接着以完全普通的方式进行编译.这种巧妙的发式将查询集合到了语言中,而无须把语义改得乱七八糟 LINQ的介绍 LINQ中的基础概念 降低两种数据 ...

  5. NHibernate系列文章二十五:NHibernate查询之Query Over查询(附程序下载)

    摘要 这一篇文章介绍在NHibernate 3.2里引入的Query Over查询,Query Over查询跟Criteria查询类似.首先创建IQueryOver对象,然后通过调用该对象的API函数 ...

  6. 第十五节:Expression表达式目录树(与委托的区别、自行拼接、总结几类实例间的拷贝)

    一. 基本介绍 回忆: 最早接触到表达式目录树(Expression)可能要追溯到几年前使用EF早期的时候,发现where方法里的参数是Expression<Func<T,bool> ...

  7. PowerBI开发 第十五篇:DAX 表达式(时间+过滤+关系)

    DAX表达式中包含时间关系(Time Intelligence)相关的函数,用于对日期维度进行累加.同比和环比等分析.PowerBI能够创建关系,通过过滤器来对影响计算的上下文. 一,时间关系 DAX ...

  8. 2.1 LINQ的查询表达式

    在进行LINQ查询的编写之前,首先需要了解查询表达式.查询表达式是LINQ查询的基础,也是最常用的编写LINQ查询的方法. 查询表达式由查询关键字和对应的操作数组成的表达式整体.其中,查询关键字是常用 ...

  9. Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G

    code&monkey   Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...

随机推荐

  1. oracle索引再论

    ORACLE中索引的数据结构有B树结构和位图结构. 我们通常用的普通索引.反向键索引.函数索引等都是B树结构的,是树状结构:位图结构则只有叶子节点. B树索引操作有唯一性扫描,范围扫描,快速索引全扫描 ...

  2. 【转】如图,win7登陆界面,键盘失灵,没办法登陆。求解!如何在这个界面打开个鼠标可以点的软键盘

    原文网址:http://zhidao.baidu.com/link?url=URPzHJXt9_yhtE-2A89apKsn5Y1B9O2NR_mktkaHSOPbUWb7TKSIYJKj_-lYPn ...

  3. 【转】Android编译系统详解(三)——编译流程详解

    原文网址:http://www.cloudchou.com/android/post-276.html 本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接 1.概述 编译Androi ...

  4. (转载)[MySQL技巧]INSERT INTO… ON DUPLICATE KEY UPDATE

    (转载)http://blog.zol.com.cn/2299/article_2298921.html MySQL 自4.1版以后开始支持INSERT … ON DUPLICATE KEY UPDA ...

  5. CentOS下安装gcc和gdb

    我的操作系统是CentOS6.4,安装源里自带了gcc4.4.0和gdb7.0,版本略老遂删除之重新安装. gcc 1.下载源码包,解压 //下载 wget http: //ftp.gnu.org/g ...

  6. 【转】Unity 相关经典博客资源总结(持续更新)

    原文:http://blog.csdn.net/prothi/article/details/20123319 就作为一个记录吧,把平时看过的Unity相关的一些好的Blog记录并分享. 好的论坛: ...

  7. ASP.net:截取固定长度字符串显示在页面,多余部分显示为省略号

    方法一: public static string GetString(string str, int length) { int i = 0, j = 0; foreach(char chr in ...

  8. Java虚拟机基础知识

    写在前面 之前老大让做一些外包面试,我的问题很简单: 介绍一下工作中解决过比较有意思的问题. HashMap使用中需要注意的点. 第一个问题主要是想了解一下对方项目经验的含金量,第二个问题则是测试下是 ...

  9. [置顶] c# asp.net 修改webconfig文件 配置

    c# asp.net 修改webconfig文件 配置 #region 修改config文件 /// <summary> /// 修改config文件(AppSetting节点) /// ...

  10. HTML Canvas 鼠标画图

    原文来自:http://www.williammalone.com/articles/create-html5-canvas-javascript-drawing-app(已被墙) 译文: http: ...