学习一门新的语言,总要从其语法学起。为了方便大家学习,同时也为了自己学习,就将ScottGu的这篇关于3.5中增加的新的特性帖子译了出来。这次我翻译采用中汉对照的方式,不然像上次译的那个那样的话不但我自己译起来不方便,大家读起来也觉得不爽。
照原来,先将原贴地址贴出:http://weblogs.asp.net/scottgu/archive/2007/04/21/new-orcas-language-feature-query-syntax.aspx
Now Let's Begin:
Last month I started a series of posts covering some of the new VB and C# language features that are coming as part of the Visual Studio and .NET Framework "Orcas" release. Here are pointers to the first three posts in my series:
上个月我开始了一系列关于作为VS和.NET Framework "Oracs" release版一部分的VB和C#语言的新特性的帖子。下面是该系列的前三个帖子的链接:
- Automatic Properties, Object Initializer and Collection Initializers
- Extension Methods
- Lambda Expressions
Today's blog post covers another fundamental new language feature: Query Syntax.
今天的帖子涵盖了新语言特性的基础:查询语法
What is Query Syntax ?
什么是查询语法?
Query syntax is a convenient declarative shorthand for expressing queries using the standard LINQ query operators. It offers a syntax that increases the readability and clarity of expressing queries in code, and can be easy to read and write correctly. Visual Studio provides complete intellisense and compile-time checking support for query syntax.
查询语法是用标准的LINQ查询操作符来声来为表达式声明的一个方便的速记方法。它提供了一种增强了程序的可读性和明了性的语法,并且易读易写,写的过程中不容易出现错误。VS为查询语法提供了完全的智能提示和编译时检查。
Under the covers the C# and VB compilers take query syntax expressions and translate them into explicit method invocation code that utilizes the new Extension Method and Lambda Expression language features in "Orcas".
根据报道,C#和VB编译器识别查询语法表达式并将它们译为强制声明的方法,这些新方法利用了在"Orcas"中的新的"Extension Method"和Lambda Expression。
Query Syntax Example:
查询语法事例:
In my previous language series posts, I demonstrated how you could declare a "Person" class like below:
在我本系统的前几篇帖子中,我说明了如何声明一个像下边的类:
We could then use the below code to instantiate a List<Person> collection with people values, and then use query syntax to perform a LINQ query over the collection and fetch only those people whose last name starts with the letter "G", sorted by the people's firstname (in ascending order):
我们可以利用下面的代码来实例化一个具有peopeo的values的List<Person>,然后利用查询语法在该collection中用LINQ查询查询出那些姓是以“G”字母开头的人,并按名排序(倒序):
The query syntax expression above is semantically equivalent to the below code that uses LINQ extension methods and lambda expressions explicitly:
上边的查询语法在语义上和下面显示调用LINQ extension methods和lambda expressions的代码是等同的:
The benefit with using the query syntax approach is that it ends up being a little easier to read and write. This is especially true as the expression gets richer and more descriptive.
用查询语法的好处是它更易读和易写一些,显然是这样,因为表达式变得更具有表达性。
Query Syntax - Understanding the From and Select Clauses:
查询语法:了解From和Select的结构:
Every syntactic query expression in C# begins with a "from" clause and ends with either a "select" or "group" clause. The "from" clause indicates what data you want to query. The "select" clause indicates what data you want returned, and what shape it should be in.
在C#中,每一个查询表达式的语法是以"from"开始,以“select”或“group"结束。"from"关键字表明了你想查询哪些数据,"select"关键字表明了你想返回哪些数据,这些数据以什么类型返回。
For example, let's look again at our query against the List<Person> collection:
例如,让我们看一下对于List<Person>集合的查询:
In the above code snippet the "from p in people" clause is indicating that I want to perform a LINQ query against the "people" collection, and that I will use the parameter "p" to represent each item in the input sequence I am querying. The fact that we named the parameter "p" is irrelevant - I could just have easily named it "o", "x", "person" or any other name I wanted.
在上边的代码片断中,"from p in peple"声明了我想从people集合中查询出数据,并且声明了将以参数"p"来代码我查询的每一个数据项。事实上我们将该参数命名为"p"跟查询是无任何关联的,我可以将它命名为"o","x","person"或其他任何我想命名的名字都可以。
In the above code snippet the "select p" clause at the end of the statement is indicating that I want to return an IEnumerable sequence of Person objects as the result of the query. This is because the "people" collection contains objects of type "Person", and the p parameter represents Person objects within the input sequence. The datatype result of this query syntax expression is consequently of type IEnumerable<Person>.
在上边的代码片断中,表达式末尾的“select p"语句声明了我想返回一个IEnumerable Person对象来作为我查询的结果。这是因为"people"集合包括了"Person"类型的对象,并且参数"p"代表了Person对象。因此,该查询表达式的返回的数据结果的类型是IEnumberable<Person>。
If instead of returning Person objects, I wanted to return just the firstnames of the people in the collection, I could re-write my query like so:
如果我不返回一个Person对象,只想返回collectioin中所有人的名字的集合,可以按照如下方式重写该查询语句:
Note above how I am no longer saying "select p", but am instead saying "select p.FirstName". This indicates that I don't want to return back a sequence of Person objects - but rather I want to return a sequence of strings - populated from each Person object's FirstName property (which is a string). The datatype result of this query syntax expression is consequently of type IEnumerable<string>.
注意上边我没再用“select p",而是用"select p.FirstName"。这表明我不想让查询返回Person对象,而是想返回一个字符串--这个字符串是Person这个对象的FirstName属性(是个字符串)中取得的。因此,该查询表达式返回值的数据类型是IEnumerable<string>。
Sample Query Syntax Against a Database
关于对数据库查询的事例:
The beauty of LINQ is that I can use the exact same query syntax against any type of data. For example, I could use the new LINQ to SQL object relational mapper (ORM) support provided in "Orcas" to model the SQL "Northwind" database with classes like below (please watch my video here to learn how to-do this):
LINQ的美在于我可以对任何类型的数据使用完全一致的查询语法。比如,我可以用在"Orcas"中提供的新的LINQ to SQL ORM来构建SQL 的"Northwind"数据库,如下图:((请看我的视频来学习如何做:watch my video here ):
Once I've defined the class model above (and its mapping to/from the database), I can then write a query syntax expression to fetch all products whose unitprice is greater than $99:
一旦我定义了如上的类模型(和它的 从/向 数据库中绘制出来的图),我可以写一个查询语句,查找出unitprice比99$多的产品来:
In the above code snippet I am indicating that I want to perform a LINQ query against the "Products" table on the NorthwindDataContext class created by the ORM designer in Visual Studio "Orcas". The "select p" indicates that I want to return a sequence of Product objects that match my query. The datatype result of this query syntax expression is consequently of type IEnumerable<Product>.
在上边的代码片断中,我指明了我想对用ORM设计器生成的NorthwindDataContext类的"Products"表进行查询。"select p"表达了我想返回跟我的查询想匹配的Product对象。因此,该查询表达式的返回值类型为IEnumerable<Product>.
Just like with the previous List<Person> collection query syntax example, the C# compiler will translate our declarative query syntax into explicit extension method invocations (using Lambda expressions as the arguments). In the case of the above LINQ to SQL example, these Lambda expressions will then be converted into SQL commands and evaluated within SQL server (so that only those Product rows that match the query are returned to our application). Details on the mechanism that enables this Lambda->SQL conversion can be found in my Lambda Expressions blog post under the "Lambda Expression Trees" section.
就像刚才的查询事例List<Person>集合那样,C#编译器将会把我们的声明语句翻译成显示的Extension method的调用(将Lambda表达式作为参数)。在上边的LINQ to SQL事例中,这些Lambda表达式将会被转换成SQL命令,并且在SQL Server中进行查询优化(如此,向我们的应用程序中只返回那些匹配了我们查询条件的Product记录)。关于此Lambda转换为SQL的机制的详细资料,在Lambda Expressions blog post 的"Lambda Expression Trees"小节下可以找到.
Query Syntax - Understanding the Where and OrderBy Clauses:
查询语法--了解Where和OrderBy语句:
Between the opening "from" clause and closing "select" clause of a query syntax expression you can use the most common LINQ query operators to filter and transform the data you are querying. Two of the most common clauses you'll end up using are "where" and "orderby". These handle the filtering and ordering of results.
在查询表达式的开始的"from"语句和结束的"select"语句之间,你可以用许多的LINQ查询操作符来过滤和转换你查询的数据。两个最常用的语句是你可以以“where"和"orderby"结束。它们处理了查询结果的过滤和排序。
For example, to return a list of alphabetically descending category names from the Northwind database - filtered to only include those categories where there are more than 5 products associated with the category - we could write the below query syntax that uses LINQ to SQL to query our database:
例如,为了返回在Northwind数据库中一个以category名称的字母倒序排列的列表,并查出跟本类别关联的产品记录多于5条的记录集,我们可以按照如下方式来写LINQ to SQL来查询数据库:
In the above expression we are adding a "where c.Products.Count > 5" clause to indicate that we only want to return category names where there are more than 5 products in the category. This takes advantage of the LINQ to SQL ORM mapping association between products and categories in our database. In the above expression I also added a "orderby c.CategoryName descending" clause to indicate that I want to sort the results in descending order.
在上边的表达戒,我们增加了"where c.Products.Count > 5"表达式来表明我们只想返回该类别的产品记录数大于5的类别列表。这利用了数据库中的products表和categories表之间的联系。在上边的表达式中我还加了"orderby c.CategoryName descending"语句来表达我想将产品结果倒序排列。
LINQ to SQL will then generate the below SQL when querying the database using this expression:
然后,LINQ to 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
Notice how LINQ to SQL is smart and only returns back the single column we need (the categoryname). It also does all of the filtering and ordering in the database layer - which makes it very efficient.
注意,LINQ to SQL是如何的精炼,并且只返回了我们想要的那一列(categoryname)。它也在数据库这一层就做了过滤和排序,这将它变得十分高效。
Query Syntax - Transforming Data with Projections
查询语法:用预测来转换数据
One of the points I made earlier was that the "select" clause indicates what data you want returned, and what shape it should be in.
刚才我提到了一点,"select"语句表明了你想返回什么数据,返回的数据是什么形式的。
For example, if you have a "select p" clause like below - where p is of type Person - then it will return a sequence of Person objects:
例如,如果有一条如下的"select p"语句-该语句中的p代表的是Person类型--那么它就是返回一个Person对象的序列:
One of the really powerful capabilities provided by LINQ and query syntax is the ability for you to define new classes that are separate from the data being queried, and to then use them to control the shape and structure of the data being returned by the query.
LINQ及其查询语法提供的一个真正强大的功能是你可以定义跟查询数据相互分离的新类,然后用定义的新类来控制查询返回的数据。
For example, assume that we define a new "AlternatePerson" class that has a single "FullName" property instead of the separate "FirstName" and "LastName" properties that our origional "Person" class had:
例如,假如我们定义一个新类"AlternatePerson",和原有类不同的时,它没有"FirstName"和"LastName",只有一个FunName属性:
I could then use the below LINQ query syntax to query my origional List<Person> collection, and transform the results to be a sequence of AlternatePerson objects using the query syntax below:
我可以用如下的LINQ查询语法来查询出原始的List<Person>集合,并将结果集转换为AlternatePerson对象:
Notice how we can use the new Object Initializer syntax I talked about in the first post in my language series to create a new AlternatePerson instance and set its properties within the "select" clause of our expression above. Note also how I am assigning the "FullName" property by concatenating the FirstName and LastName properties of our origional Person class.
注意我们是如何用我在该系列中首篇帖子中提到的用新的Object Initializer语法 来生成一个新类AlternatePerson的实例,并将它的属性在上边的“select"语句中声明。同样也要注意我如何将用拼接原有的Person类的"FirstName"和"LastName"属性的方法将"FullName"属性指定给AlternatePerson。
Using Query Syntax Projections with a Database
和数据库一起使用查询语法投影
This projection feature ends up being incredibly useful when working with data pulled from a remote data provider like a database, since it provides us with an elegant way to indicate which columns of data our ORM should actually fetch from a database.
这个投影功能在从远程获取数据时非常有用,比如从数据库中获取数据时,因为它提供给了我们一个简洁的方法来声明哪些字段的数据我们的ORM应该从数据库中取出。
For example, assume I use the LINQ to SQL ORM provider to model the "Northwind" database with classes like below:
例如,假如我用LINQ to SQL ORM生成"Northwind"数据库,如下所示:
By writing the LINQ query below, I am telling LINQ to SQL that I want a sequence of "Product" objects returned:
通过写如下的LINQ 查询语句,我告诉LINQ to SQL我想返回一个"Product"对象序列:
All of the columns necessary to populate the Product class would be returned from the database as part of the above query, and the raw SQL executed by the LINQ to SQL ORM would look like below:
所有的在Product类中的列将会作为以上查询的一部分从数据库返回,并且LINQ to SQL ORM执行的原生的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
If I didn't need/want all of these columns for some scenarios, I could alternatively define a new "MyProduct" class like below that has a subset of the properties that the Product class has, as well as one additional property - "TotalRevenue" -- that the Product class doesn't have (note: for people not familiar with C#, the Decimal? syntax indicates that UnitPrice property is a nullable value):
在某些情况下,如果我不需要,也不想返回所有的列,我可以按照如下方式来定义一个新的"MyProduct"类,该类含有Product类含有的的辅助属性,并且也有一个额外的属性”TotalRevenue"--该属性是Product类中的(不熟悉C#语法者:Decimal?表达式表明了UnitPrice属性不可为空值):
I can then use the projection capability of query syntax to shape the data I want returned from the database using a query like below:
我可以用如下的查询语句来用查询语法的投影功能来将从数据库中返回的数据“格式”一下:
This is indicating that instead of returning a sequence of "Product" objects, I instead want "MyProduct" objects, and that I only need 3 properties of them filled. LINQ to SQL is then smart enough to adjust the raw SQL to execute to only return those three needed product columns from the database:
这表明了我不想让它返回"Product"对象的序列,取而代之的是我想让它返回“MyProduct"序列,并且我只想让它有3列。LINQ to SQL将会非常智能地调整原来的SQL语句,以使得从数据库中只查询出产品的那3列:
SELECT [t0].[ProductID], [t0].[ProductName], [t0].[UnitPrice]
FROM [dbo].[Products] AS [t0]
WHERE [t0].[UnitPrice] > 99
Just to show-off, I could also populate the 4th property of the MyProduct class - which is the "TotalRevenue" property. I want this value to be the aggregate amount of revenue that our products have sold for. This value isn't stored anywhere as a pre-computed column within the Northwind database. Instead you need to perform a join between the "Products" table and the "Order Details" table and sum up all of the Order Detail rows associated with a given product.
仅仅是为了演示一下,我也可以为MyProduct类添加第4个属性--"TotalRevenue",我想用这个属性来合计产品已经卖出的利润。这个不像一个已经定义好的数据列那样存在数据库的中。你需要在"Product"表和"Order Details"表中做一个连接,并将所有和已知产品相关联的Order Details 记录行求个总和。
What is cool is that I can use the LINQ "Sum" extension method on the Product class's OrderDetails association and write a multiplication Lambda expression as part of my query syntax projection to compute this value:
酷的是,我可以用LINQ的"Sum" extension method来对Product类的OrderDetails关系做加法去处并且写一个"Lambda"表达式做为我的查询语法投影来计算该值:
LINQ to SQL is then smart enough to use the below SQL to perform the calculation in the SQL database:
LINQ to 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
Query Syntax - Understanding Deferred Execution, and using ToList() and ToArray()
查询语法--了解Deferred Execution,用ToList()和ToArray()方法
By default the result of a query syntax expression is a variable of type IEnumerable<T>. In my samples above you'll notice that all of the query syntax assignments are to IEnumerable<Product>, IEnumerable<string>, IEnumerable<Person>, IEnumerable<AlternatePerson>, and IEnumerable<MyProduct> variables.
默认的,查询表达式的结果是一个类型为IEnumerable<T>的变体类型,在我以上的例子中你注意到所有的表达式声明语句是IEnumerable<Product>, IEnumerable<string>, IEnumerable<Person>, IEnumerable<AlternatePerson>, and IEnumerable<MyProduct> variables.
One of the nice characteristics of IEnumerable<T> interfaces is that objects that implement them can defer the actual execution of the queries until a developer first attempts to iterate over the values (this is accomplished using the "yield" construct that was first introduced with C# 2.0 in VS 2005). LINQ and query syntax expressions take advantage of this feature, and defer the actual execution of queries until the first time you loop over the results. If you never iterate over the IEnumerable<T> result, then the query is never executed.
IEnumeralb<T>接口的其中一个特点是实现它们的对象可以延迟查询的执行,直到开发者首次使用该声明对象(这是在VS2005中用C#2.0介绍的"yield"结构)。LINQ 和查询语法表达式利用了该特性,并且延迟了查询,直到你首次遍历结果集。如果你一直不用IEnumerale<T>结果集,它就一直不会执行。
For example, consider the below LINQ to SQL example:
例如,看如下的LINQ to SQL事例:
The database will be hit and the values to populate our Category objects will be retrieved not when the query syntax expression is declared - but rather when we first try and loop over the results (indicated above with the red arrow).
不是在查询语法声明时将数据库查询和将值加载到我们的Category对象中,而是当我们首次试图循环结果集时(上边用红色箭头表明的部分)
This deferred execution behavior ends up being really useful because it enables some powerful composition scenarios where we can "chain" multiple LINQ queries and expressions together. For example, we could feed the result of one expression into another - and by deferring the execution allow an ORM like LINQ to SQL to optimize the raw SQL based on the entire expression tree. I'll show examples of how to use this in a later blog post.
这种延迟执行的行为的非常有用,因为它使得一些在我们能“链接”LINQ 查询和表达式到一起的强大的组合情景成为可能。例如,我们可以将一个表达式的填充到另一个中--并且通过延迟执行,允许像LINQ to SQL的ORM根据整个表达树来优化原始的SQL语句。在下几篇博客中,我将展示一些如何使用延迟执行的例子。
How to evaluate the query syntax expression immediately
如何快速评估查询表达式
If you don't want to defer the execution of queries, and instead want to execute them immediately, you can use the built-in ToList() and ToArray() operators to return either a List<T> or an array containing the results.
如果你不想延迟执行查询,想立即执行它们,你可以用内置的ToList()和ToArray()方法来返回一个包含结果集的或者List<T>或者是array的类型。
For example, to return a generic-based List<T> collection:
例如,返回一个基于List<T>的集合:
and to return an array:
返回一个数组:
In both cases above the database will be hit and the Category objects populated immediately.
在上边这两个例子中,将会立即查询数据库,并且将查询结果填充到Category对象中。
Summary
总结
Query syntax provides a very convenient declarative shorthand for expressing queries using the standard LINQ query operators. It offers a syntax that is very readable, and which works against any type of data (any in-memory collection, array, XML content, or against remote data providers like databases, web-services, etc). Once you become familiar with the syntax, you can immediately apply the knowledge everywhere.
查询语法是用标准的LINQ查询操作符来声来为表达式声明的一个方便的速记方法。它提供了一种增强了程序的可读性和明了性的语法,并且易读易写,写的过程中不容易出现错误。VS为查询语法提供了完全的智能提示和编译时检查。
In the not too distant future I'll finish the last segment of this language series - which will cover the new "anonymous types" feature. I'll then move on to cover some super practical examples of using all of these language features in the real world (especially using LINQ against databases and XML files).
很快我将结束该语言的最后一个部分--该部分涵盖了新的“anonymous types"特性。届时我将用这些语言在现实世界中做一些高级实用的例子(尤其是用LINQ来对数据库和XML文件来操作)
Hope this helps,
Scott