新Orcas语言特性-查询句法

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

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

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

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

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

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

查询句法的例子:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

在上面的表达式里,我们加了 "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对象:

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

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

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

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

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

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

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

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

填充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值):

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

这表明,不是返回一串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表达式,来计算这个值:

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<T>。在上面的例子里,你会注意到所有的查询句法赋值是给IEnumerable<Product>, IEnumerable<string>, IEnumerable<Person>, IEnumerable<AlternatePerson>, 和 IEnumerable<MyProduct> 变量的。

IEnumerable<T>接口的一个很好的特征是,实现它们的对象可以把实际的查询运算延迟到开发人员第一次试图对返回值进行迭代(这是通过使用最早在VS 2005中C# 2.0 中引进的yield构造来达成的)时才进行。LINQ和查询句法表达式利用了这个特性,将查询的实际运算延迟到了你第一次对返回值进行循环时才进行。假如你对IEnumerable<T>的结果从不进行迭代的话,那么查询根本就不会执行。

譬如,考虑下面这个LINQ到SQL的例子:

不是在查询句法表达式声明的时候,而是在我们第一次试图对结果进行循环(上面红箭头标志的地方),才会去访问数据库以及取出填充Category对象所需的值。

这个延迟运算的行为结果变得非常有用,因为它促成了一些把多个LINQ查询和表达式链在一起的强有力的组合场景。譬如,我们可以把一个表达式的结果喂给另一个表达式,然后通过延迟运算,允许象LINQ 到SQL这样的ORM根据整个表达式树来优化raw SQL。我将在以后的一个博客贴子里对这样的场景做示范说明。

如何立刻对查询句法表达式做运算

如果你不要延迟查询运算,而是要对它们立刻就执行运算,你可以使用内置的ToList() 和ToArray() 运算符来返回一个包括了结果集的List<T>或者数组。

譬如,要返回一个基于范型的 List<T> 集合的话:

要返回一个数组的话:

在上面两种情形下,会立刻访问数据库,填充Category对象。

结语

查询句法在使用标准的LINQ查询运算符来表达查询时,提供了非常方便的声明式简化写法。它提供的句法可读性非常高,可以针对任何类型的数据(内存中的集合,数组,XML内容,以及象数据库这样的远程数据提供器,web服务等等)进行查询。一旦你熟悉这个句法后,你可以在任何地方应用这个知识。

在不远的将来,我将结束本语言系列的最后一部分,该部分将讨论新的匿名类型特性。然后我将转而讨论在实际应用中使用所有这些语言特性的一些非常实用的例子(特别是针对数据库和XML文件使用LINQ的例子)。

希望本文对你有所帮助,

Scott

(0)

相关推荐

  • 新Orcas语言特性-查询句法

    [原文地址]New "Orcas" Language Feature: Query Syntax [原文发表日期] Saturday, April 21, 2007 2:12 上个月我开始了一个贴子系列,讨论作为Visual Studio和.NET框架Orcas版本一部分发布的一些新的VB和C#语言特性.下面是该系列的前三篇贴子的链接: 自动属性,对象初始化器,和集合初始化器 扩展方法 Lambda表达式 今天的贴子要讨论另一个基础性的新语言特性:查询句法(Query Syntax).

  • Ruby的语法和语言特性总结

    Ruby是一种解释型.面向对象.动态类型的语言.Ruby采取的策略是在灵活性和运行时安全之间寻找平衡点.随着Rails框架的出现,Ruby也在2006年前后一鸣惊人,同时也指引人们重新找回编程乐趣.尽管从执行速度上说,Ruby谈不上有多高效,但它却能让程序员的编程效率大幅提高.本文将讲述Ruby语言的基础语言特性,包括基本的语法及代码块和类的定义. 1. 基础 在Ruby交互命令行中输入以下命令(>>为命令行提示符,=>为返回值:下文将把=>符号和语句写在一行内表明其返回值): &

  • PHP 7.0新增加的特性介绍

    ?? 运算符(NULL 合并运算符) 把这个放在第一个说是因为我觉得它很有用.用法: $a = $_GET['a'] ?? 1;它相当于: <?PHP $a = isset($_GET['a']) ? $_GET['a'] : 1; 我们知道三元运算符是可以这样用的: $a ?: 1但是这是建立在 $a 已经定义了的前提上. ?? 运算符(NULL 合并运算符) 把这个放在第一个说是因为我觉得它很有用.用法: $a = $_GET['a'] ?? 1; 它相当于: <?php $a = iss

  • oracle sql语言模糊查询--通配符like的使用教程详解

    oracle在Where子句中,可以对datetime.char.varchar字段类型的列用Like子句配合通配符选取那些"很像..."的数据记录,以下是可使用的通配符: %   零或者多个字符 _    单一任何字符(下划线) \     特殊字符 oracle10g以上支持正则表达式的函数主要有下面四个: 1,REGEXP_LIKE :与LIKE的功能相似 2,REGEXP_INSTR :与INSTR的功能相似 3,REGEXP_SUBSTR :与SUBSTR的功能相似 4,RE

  • GO语言类型查询类型断言示例解析

    目录 类型查询 1.comma-ok断言 2. switch测试 类型断言 类型查询 我们知道interface的变量里面可以存储任意类型的数值(该类型实现了interface).那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法: comma-ok断言 switch测试 1.comma-ok断言 Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,elem

  • 理解Javascript的动态语言特性

    Javascript是一种解释性语言,而并非编译性,它不能编译成二进制文件. 理解动态执行与闭包的概念 动态执行:javascript提供eval()函数,用于动态解释一段文本,并在当前上下文环境中执行. 首先我们需要理解的是eval()方法它有全局闭包和当前函数的闭包,比如如下代码,大家认为会输出什么呢? var i = 100; function myFunc() { var i = 'test'; eval('i = "hello."'); } myFunc(); alert(i

  • 浅谈关于JavaScript的语言特性分析

    前言在JavaScript中,作用域.上下文.闭包.函数等算是精华中的精华了.对于初级JSer来说,是进阶必备.对于前端攻城师来说,只有静下心来,理解了这些精华,才能写出优雅的代码. 本文旨在总结容易忘记的重要知识,不会讲基本的概念.如果对基本知识不太熟悉,就去翻下< JavaScript权威指南>吧~ 语言特性函数表达式 先看代码段: 复制代码 代码如下: [javascript] view plaincopyprint?var f = function foo(){      return

  • C语言中查询进程信号是否被遮罩或搁置的简单方法

    C语言sigprocmask()函数:查询或设置信号遮罩 头文件: #include <signal.h> 定义函数: int sigprocmask(int how, const sigset_t *set, sigset_t * oldset); 函数说明:sigprocmask()可以用来改变目前的信号遮罩, 其操作依参数how 来决定: 1.SIG_BLOCK 新的信号遮罩由目前的信号遮罩和参数set 指定的信号遮罩作联集 2.SIG_UNBLOCK 将目前的信号遮罩删除掉参数set

  • C语言实现查询自动售货机中的商品价格【实例分享】

    问题描述:假设自动售货机出售四种商品,在屏幕上显示以下菜单(编号和选项),用户可以连续查询商品的价格,当查询次数超过5次时,自动退出查询:不到5次时,用户可以选择退出.当用户输入编号1~4,显示相应的商品价格(保留1位小数):输入0,退出查询:输入其他编号,显示价格为0. int choice,i; double price; for(i=1;i<=5;i++){ //以下5行显示菜单 printf("[1]Select crisps\n"); //查询薯片价格 printf(&

  • 奇怪的C语言特性

    下面列出的特性未必奇怪,有的算是有趣. 1)a[2] 等价于 2[a] "aabbccdd"[5] 等价于 5["aabbccdd"] 这条特性可以用于使用数组.指针.字符串,但不能用在变量定义时.K&R C Programming language 217页对此有介绍. 2)二元.三元复合字符 http://en.wikipedia.org/wiki/Digraphs_and_trigraphs 字符串字面值??!将被认为是|,所以两个问号同时出现在字符串

随机推荐