Welcome

首页 / 软件开发 / LINQ / 从查询表达式开始认识LINQ

从查询表达式开始认识LINQ2013-11-11 cnblogs 文酱学习和使用C#已经有2个月了,在这两个月的学习中,深刻体会到,C#这门语言还真不适合编程初学者学习 ,因为它是吸取了很多其他语言,不仅是面向对象,还包括函数式语言的很多特性,导致它变成特性大爆炸的 语言。它的许多方面单独拿出来讲,就得是一本书的规模,而且还不一定让人一下子明白。

LINQ,Language INtegrated Query,语言集成查询,是其中一个非常重要的部分,有关它的功能增强, 贯穿了整个C#的发展。

先从基本的查询表达式下手。

在讲查询表达式前,我们必须明白:查询 表达式不仅仅是针对数据库,它针对的是所有数据源,因为LINQ的意图就是为所有数据源提供统一的访问方式 。因为最近的项目使用的是LINQ to SQL,所以这里只讲LINQ to SQL。

查询表达式非常像SQL语句,但 书写方式却是反过来:

var articles = from article in db.Articleswhere a.Name == "JIM"select article;
我们必须明白,查询表达式返回的结果是一个序列,哪怕这个序列 只有一个元素。

正因为查询表达式返回的是一个序列,才使得它的行为非常有趣。序列的基本特点就 是每次只取一个元素,这使得每个转换都处理一个数据,而且只在结果序列的第一个元素被访问的时候才会开 始执行查询表达式。像是上面的数据流是这样的:select转换会在它返回的序列中的第一元素被访问时,为该 元素调用where转换,where转换会返回数据列表中第一个元素,检查这个谓词(a.Name == "JIM") 是否匹配,再把该元素返回给select。

这就是查询表达式的延迟执行,在它被创建的时候没有处理任 何数据,只是在内存中生成了这个查询的委托。这样子是非常高效和灵活的,但并不是所有情况都适合,像是 reverse这类的操作,就要访问整个数据源。所以,我们一般在返回另一个序列的操作中使用延迟执行,如果 是返回单一值就使用即时执行。

使用查询表达式,首先就是声明数据序列的数据源:

from article in db.Articles

where子句用来进行过滤。它会进入数据流中每个元素的谓词,只有返回true 的元素才能出现在结果序列中。我们可以使用多个where子句,只有满足所有谓词的元素才能进入结果序列。

编译器会将where子句转换为带有Lambda表达式的where方法调用,像是这样:

where(article => article.Name == "JIM")

所以我们也可以直接使用Lambda表达式,它同样是返回一 个序列:

var articles = db.Articles.Where(article => article)

使用Lambda表达式能使 我们的代码的简洁度大幅上升,这也是它为什么会被引进C#中的原因之一。

select子句就是投影,相 信学过数据库的同学一定非常熟悉。它同样也会有对应的方法调用,应该说,几乎所有的LINQ to SQL操作都 有对应的方法调用(因为编译器就是讲它们转换为方法调用),所以接下来就不再讲方法调用形式了。

前面讲过,编译器会将查询表达式转换为普通C#代码的方法调用,以select为例,它并不会像我们预期的那样 ,转换为Enumerable.Select或者List<T>.Select,它就只是对代码进行转换,然后再寻找适当方法。 该方法的参数会是一个委托类型或者一个Expression<T>。为什么转换后的方法中的参数是一个Lambda 表达式呢?因为Lambda表达式可以被转换为委托实例或者表达式树,所以使用Lambda表达式就可以对应所有情 况。这种转换并不依赖与特定类型,而只依赖与方法名称和参数,也就是所谓的动态类型(Duck Typing)的编 译时形式。

因为这样,我们可以实现自己的LINQ提供器,但除非真的有特殊需要,一般我们都不需要 做到这点。

我们来看看查询表达式中两个最重要的组成:范围变量和投影表达式。

from article in db.Articles

其中,article就是范围变量,而:

select article

投影表 达式就使用了该范围变量。编译器在转换的时候,Lambda表达式的左边,参数名称就是范围变量,右边来自于 投影表达式,所以,我们决不能这样写:

from article in db.Articlesselect person
从上面来看,范围变量应该是隐式类型,编译器会自己推断它的具体类型。当然, 我们也可以使用显式类型的范围变量,像是下面这样:

from Article article in db.Articlesselect article
其中,db.Articles是一个List<Article>。

这样的情况是因为我们想 要在强类型的容器中进行查询。这样的机制能够运行的保障源于一个方法:Cast()。Cast()会强制将类型转换 为目标类型,如果无法转换则会出错。

上面的表达式会转换为以下的代 码:

list.Cast<Article>().Select(article => article);

为什么需要我们对范围变 量进行显式声明呢?因为Select方法只是IEnumerable<T>的扩展方法,而不是IEnumerable,所以我们 必须要显式的声明范围变量的类型才能调用Select方法。