[不收集不行]新Orcas语言特性:查询句法

from http://weblogs.asp.net/scottgu
         http://blog.joycode.com/scottgu/archive/2007/04/24/101654.aspx

【原文地址】New "Orcas" Language Feature: Query Syntax
【原文发表日期】 Saturday, April 21, 2007 2:12

上个月我开始了一个贴子系列,讨论作为Visual Studio和.NET框架Orcas版本一部分发布的一些新的VB和C#语言特性。下面是该系列的前三篇贴子的链接:

  • 自动属性,对象初始化器,和集合初始化器
  • 扩展方法
  • Lambda表达式

今天的贴子要讨论另一个基础性的新语言特性:查询句法(Query Syntax)

什么是查询句法(Query Syntax)?

查询句法是使用标准的LINQ查询运算符来表达查询时一个方便的声明式简化写法。该句法能在代码里表达查询时增进可读性和简洁性,读起来容易,也容易让人写对。Visual Studio 对查询句法提供了完整的intellisense和编译时检查支持。

在底下,C#和VB编译器则把查询句法的表达式翻译成明确的方法调用代码,这样的代码利用了Orcas中的新的扩展方法和Lambda表达式语言特性。

查询句法的例子:

在我以前的语言系列贴子里,我示范了你可以象下面这样声明一个Person类:

step1.jpg

 

然后我们可以使用下面这样的代码,用一些个人信息来生成一个List集合实例,然后使用查询句法来对该集合做一个LINQ查询,只取出那些姓(last name)的首字母为G的人,按名字(first name)来排序(升序):

step2.jpg

上面查询句法的表达式在语意上与下面明确使用LINQ扩展方法和Lambda表达式的代码是等同的:

step3.jpg

使用查询句法方法的好处是,结果会是稍微容易读写些,这在表达式变得更繁复时尤其如此。

查询句法 - 理解from和select子句:

在C#中,每个查询表达式的句法从from子句开始,以select或group子句结束。from子句表示你要查询什么数据。select子句则表示你要返回什么数据,且应该以什么构形返回。

譬如,让我们再来看一下我们对List集合的查询:

step18.jpg

在上面的代码片段里,"from p in people"表示了我要对"people" 这个集合做一个LINQ查询,我将用参数"p"代表我正查询的输入序列的每个项。我们将参数命名为"p" 这个事实是无关紧要的,我完全可以很容易地将其命名为"o", "x", "person"或我想要的任何名字。

在上面的代码片段里,语句结尾的"select p"子句表示,作为查询的结果,我要返回一个Person对象的IEnumerable序列。这是因为"people"集合包含了Person类型的对象,而参数p则代表了输入序列中的Person对象。因此,该查询句法表达式的结果数据类型是IEnumerable

假如不是返回Person对象,我想返回该集合中的人的名字,我可以把查询改写成这样:

step4.jpg

注意上面我不再说"select p",而是说"select p.FirstName"。这表示我不想返回一串Person对象,而是想返回一串字符串,由Person对象的FirstName属性(该属性是个字符串)填充而来。 因此,该查询句法表达式的结果类型是 IEnumerable

针对数据库的查询句法的例子

LINQ的妙处在于,我可以针对任何数据类型使用完全一样的查询句法。譬如,我可以使用Orcas提供的新LINQ到SQL对象关系映射器支持,对SQL服务器的Northwind数据库进行建模,生成下面这些类(请观看我这里的录像来学习该如何实现):

step5.jpg

在上面定义好类模型之后(以及它与数据库间的映射关系),然后我就可以写个查询句法的表达式取出那些单价大于99元的产品:

step6.jpg

在上面的代码片段里,我表示我要对NorthwindDataContext类的Products表进行一个LINQ查询,NorthwindDataContext类是由Visual Studio Orcas的ORM设计器生成的。"select p"表示我要返回匹配我的查询的一串Product对象,因此,该查询句法表达式的结果数据类型是IEnumerable

就象前面List查询句法的例子一样,C# 编译器会把我们的声明式查询句法翻译成明确的扩展方法调用(使用Lambda表达式作为参数)。在上面的LINQ到SQL的例子的情形下,这些Lambda表达式会被转化成SQL命令,然后在SQL服务器上做运算(这样,只有那些匹配查询条件的Product记录行会返回到我们的应用中)。促成这个Lambda->SQL 转化的机制的细节可见于我的Lambda表达式博客贴子的"Lambda表达式树"部分。

查询句法 - 理解where和orderby子句:

在一个查询句法表达式开头的"from" 子句和结尾的"select"子句之间,你可以使用最常见的LINQ查询运算符来过滤和转换你在查询的数据。两个最常用的子句是"where"和"orderby"。这两个子句处理对结果集的过滤和排序。

譬如,要从Northwind数据库里返回按字母降序排列的分类名称列表,过滤条件是只包括那些含有5个以上产品的分类,我们可以编写下面这样的查询句法来用LINQ到SQL对我们的数据库做查询:

step7.jpg

在上面的表达式里,我们加了 "where c.Products.Count > 5" 子句来表示我们只要那些含有5个以上产品的分类。这利用了数据库中产品和分类间的LINQ到SQL的ORM映射的关联。在上面的表达式中,我也加了"order by c.CategoryName descending"子句来表示我要将结果集按名称降序排列。

LINQ到SQL然后就会在使用这个表达式查询数据库时,生成下列SQL:

SELECT [t0].[CategoryName] FROM [dbo].[Categories] AS [t0]
WHERE ((
    SELECT COUNT(*)
    FROM [dbo].[Products] AS [t1]
    WHERE [t1].[CategoryID] = [t0].[CategoryID]
)) > 5
ORDER BY [t0].[CategoryName] DESC

注意,LINQ到SQL很聪明,只返回了我们所需的单个字段(分类名称), 而且它是在数据库层做了所有的过滤和排序,使得该查询效率非常高。

查询句法 - 用投影(Projection)来转换数据

先前我指出的一个要点是,"select" 子句表示了你要返回的数据,以及这个数据的构形是什么

譬如,假如你有个象下面这样的"select p" 子句,这里p的类型是Person,然后,它就会返回一串Person对象:

step2.jpg

LINQ和查询句法提供的一个非常强大的功能是允许你定义跟被查询的数据分开的新的类型,然后用新的类型来控制查询返回的数据的形状和结构。

譬如,假设我们定义了一个新的AlternatePerson类,内含一个FullName属性,而不是我们原先的Person类内的分开的FirstName和LastName属性:

step8.jpg

然后我就可以使用下面的LINQ查询句法来查询我原先的List集合,用下面的查询句法将结果转换成一串AlternatePerson对象:

step9.jpg

注意看,我们是如何在上面的表达式里的"select"子句里,使用我的语言系列的第一个贴子里讨论过的新的对象初始化器句法来创建新的AlternatePerson实例,同时设置它的属性的。也注意我是如何连接我们原先Person类的FirstName和LastName属性,然后将其赋值给FullName属性的。

对数据库使用查询句法投影

这个投影特性在操作从象数据库这样一个远程数据提供器那里取回的数据时,会变得难以置信地有用,因为它提供给我们一个优雅的方式,来表示我们的ORM应该从数据库实际取回哪些数据字段。

譬如,假设我用了LINQ到SQL的ORM提供器对Northwind数据库建模,生成下面这些类:

step10.jpg

通过编写下面这个LINQ查询,我告诉LINQ到SQL我要返回一串Product对象:

step11.jpg

填充Product类所需的所有字段都将作为上面查询的一部分从数据库中返回,由LINQ到SQL ORM执行的raw SQL看上去象下面这样:

SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID],
              [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock],
              [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
FROM [dbo].[Products] AS [t0]
WHERE [t0].[UnitPrice] > 99

在一些场景下,我不需要也不用所有这些字段,我可以定义一个下面这样的新的MyProduct类,只拥有Product类具有的部分属性,以及一个Product类并不具有的额外属性,TotalRevenue (注: 对那些不熟悉C#的,Decimal?句法表示我们的UnitPrice属性是个nullable值):

step12.jpg 

然后我就可以使用下面这个查询,使用查询句法的投影功能来构造我要从数据库返回的数据的形状:

step13.jpg

这表明,不是返回一串Product对象,我要MyProduct对象,我只要其中三个属性被赋值,LINQ到SQL就会很聪明地调整要执行的raw SQL语句,从数据库只返回那三个需要的产品字段:

SELECT [t0].[ProductID], [t0].[ProductName], [t0].[UnitPrice]
FROM [dbo].[Products] AS [t0]
WHERE [t0].[UnitPrice] > 99

为炫耀起见,我也可以填充MyProduct类的第四个属性,即TotalRevenue属性。我要这个值等于我们产品目前的销售额的总量。这个值在Northwind数据库中并没有作为一个预先算好的字段而存在。而是,你需要在Products表和Order Details表间做一个关联,然后计算出一个给定产品对应的所有的Order Detail 行的总量。

非常酷的是,我可以在Product类的OrderDetails关联上使用LINQ的 Sum 这个扩展方法,编写一个作为我的查询句法投影一部分的乘法Lambda表达式,来计算这个值:

step14.jpg

LINQ到SQL就会非常聪明地使用下面这个SQL在SQL数据库里做运算:

SELECT [t0].[ProductID], [t0].[ProductName], [t0].[UnitPrice], (
        SELECT SUM([t2].[value])
        FROM (
                 SELECT [t1].[UnitPrice] * (CONVERT(Decimal(29,4),[t1].[Quantity])) AS [value], [t1].[ProductID]
                 FROM [dbo].[Order Details] AS [t1]
                 ) AS [t2]
        WHERE [t2].[ProductID] = [t0].[ProductID]
        ) AS [value]
FROM [dbo].[Products] AS [t0]
WHERE [t0].[UnitPrice] > 99

查询句法 - 理解延迟执行(Deferred Execution)和使用ToList() 和ToArray()

在默认情形下,查询句法表达式的结果的类型是IEnumerable。在上面的例子里,你会注意到所有的查询句法赋值是给IEnumerable, IEnumerable, IEnumerable, IEnumerable

C#底层是如何实现Linq的(Extension Methods, Lambda Expression, Expression Tree, yadayada...),做了一些小实验:

转载于:https://www.cnblogs.com/lxinxuan/archive/2007/04/24/725952.html


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部