博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

4章:LINQ句法基础

现代编程语言和软件开发架构越来越多的采用基于面向对象的设计、开发方式。因此,经常我们更多的需要查询和管理对象和集合中的对象来代替直接操作数据记录和数据表。因此我们需要独立于特定的数据源或数据持久层的开发工具和语言。 语言整合查询(LINQ)提供了一种独立与传统开发语言持久层介质的解决方案,使得开发者可以查询和管理程序中对象(objects, entities, database records, XML nodes,等等)的序列。LINQ的最关键特性是:它再最大程度上整合了常见的开发语言(Microsoft 的开发语言),通过一种通用的表达式就可以操作开发中遇到的各种各样的对象。

在这章里, 我们将通过讲述LINQ中最主要的类和操作符,来让大家对它的语法和架构有一个认识。正如我们在“第一章:LINQ简介”中讲到的,LINQ的基本架构支持多种类型查询引擎(LINQ to ObjectsLINQ to SQLLINQ to DataSet, LINQ to Entities, LINQ to XML, 等等)。这些所有的查询功能都是基于特定的扩展方法,这些你都将在本章了解到。本章所采用的的例子主要都是(LINQ to Objects), 这样有利于我们把注意力集中在LINQ查询和操作符本身,而不是把注意力分散到LINQ的各种各样的调味间的差异或者内部细节实现上。接下来的“第五章:LINQ to ADO.NET”和“第六章:LINQ to XML”,我们才会更深入的探究LINQ操作符,来揭示LINQ各种查询间的差异。

LINQ查询

LINQ提供了一套通过扩展方法定义的查询操作者,由于实现IEnumerable<T>接口,这些操作符适用于任何一种对象。 (了解更多关于扩展方法的知识,请参阅Chapter 2, “C# Language Features,” Chapter 3, “Microsoft Visual Basic 9.0 Language Features.”)LINQ作为一种通用询问的框架,任何开发者能通过LINQ操作自己定义的实现IEnumerable<T>接口的List,同时基于框架强大的扩展性,开发者能定义自己的数据类型的扩展方法。例如,LINQ to SQL LINQ to XML 就通过扩展方式分别定义了LINQ 的操作符来各自的关系数据和XML 节点。

查询语法

为了理解查询与法,我们先从一个简单的例子开始。

思考一下下面的Developer类型:

 public class Developer {

    public string Name;

    public string Language;

    public int Age;

}

想象一下这样的情况,要查询一个存储Developer对象的数组,选出使用C# 作为开发语言的那些开发者。使用LINQ to Objects,代码可能如4-1

4-1:一简单的LINQ查询

using System;

using System.Linq;

using System.Collections.Generic;

class app {

    static void Main() {

        Developer[] developers = new Developer[] {

            new Developer {Name = "Paolo", Language = "C#"},

            new Developer {Name = "Marco", Language = "C#"},

            new Developer {Name = "Frank", Language = "VB.NET"}};

        IEnumerable<string> developersUsingCsharp =

            from   d in developers

            where d.Language == "C#"

            select d.Name;

        foreach (string item in developersUsingCsharp) {

            Console.WriteLine(item);

        }

    }

}

这条代码后的结果是开发者PaoloMarco

虽然它的风格和SQL有点不同,但是这段查询语句还是和SQL有几分相似。 为了了解并掌握这些新的语法,我们将逐个的来学习LINQ的操作符.

查询表达式

一个查询表达式就是一个表达式树,表达树通过一个或多个操作符(标准操作符或域说明操作符)来查询一个或多个数据源。通常,查询的结果会以一个数据列表的形式返回。只有查询出的内容可以被枚举时,一个表达式才会有返回值。

这是select表达式:

select d.Name

应用于一组对象:

from d as developers

任何from指向的对象实例的类都必须实现IEnumerable<T>接口。

选择语句使用附加了一个过滤条件:

where d.Language == "C#"

这个过滤条件直接的解析为执行Enumerable类(定义于System.Linq命名空间)Where扩展方法select语句也是一个扩展方式(叫做Select),由Enumerable类提供。

    提示:

System.Linq namespace内定义的类Enumerable提供很多LINQ to Objects查询执行所需的操作符,它们都被定义为基于IEnumerable<T>接口类型的扩展方法。

让我们在重新来思考一下,加入我们把刚才的语句中的规则转化为最本质的语法,然后重新写一遍:

IEnumerable<string> expr =

    developers

    .Where(d => d.Language == "C#")

    .Select(d => d.Name);

Where 方法和Select方法同时接受lambda 表达式作为它们的参数(要了解更多关于lambda表达方式的定义和句法,请参阅第2) 而这些lambda 表达式会被解析,然后判断该使用哪种泛型委托类型(定义于System.Linq命名空间)

    下面是所有可用的泛型委托类型:

public delegate T Func< T >();

public delegate T Func< A0, T >( A0 arg0 );

public delegate T Func< A0, A1, T > ( A0 arg0, A1 arg1 );

public delegate T Func< A0, A1, A2, T >( A0 arg0, A1 arg1, A2 arg2 );

public delegate T Func< A0, A1, A3, T > ( A0 arg0, A1 arg1, A2 arg2, A3 arg3 );

Enumerable的很多扩展方法都接受这些委托作为参数, 并且这些委托的用法将贯穿于本章所有的范例。我们把LINQ语句进行拆解,还原成最初是的代码,如:4-2

4-2:用本质的语法解释上面的LINQ 查询

Func<Developer, bool> filteringPredicate = d => d.Language == "C#";

Func<Developer, string> selectionPredicate = d => d.Name;

IEnumerable<string> expr =

    developers

    .Where(filteringPredicate)

    .Select(selectionPredicate);

C# 3.0 编译器(如Visual Basic 9.0编译器)将LINQ语句(4-1)翻译成形如4-2展示的语句。当你渐渐的掌握了LINQ,你就可以写出像4-1那样简单且便于管理的语句;当然只要你愿意,像4-2那样的代码也没有任何问题。不过有些情况下你不得不直接去呼叫扩展方法,因为查询语法中不可能包含所有用到的扩展方法。

完整的查询语法

在上一节,我们通过例子分析讲解了一个简单的对象集合查询。而在实际的应用中,LINQ查询语句会更完整和相互广联更紧密。LINQ语法是以from开头,以select或者group结尾,以select语法结尾的查询返回值是enumerable对象,以group语法结尾的查询返回值是群组(每一个群组都是一个enumerable对象)的列表。之所以LINQ不像SQL那样以select开头,而以from开头是为了在from后面的语句中能够提供Microsoft的智能提示功能,这样LINQ的开发就可以更简便。 下列代码显示的是一个完整LINQ查询表达式的原型:

query-expression ::= from-clause query-body

query-body ::=

join-clause*

(from-clause join-clause* | let-clause | where-clause)*

orderby-clause?

(select-clause | groupby-clause)

    query-continuation?

from-clause ::= from itemName in srcExpr

select-clause ::= select selExpr

groupby-clause ::= group selExpr by keyExpr

第一个from语句后可以不跟或者跟多个from,let,where语句 。let语句用于给返回值附加一个名字, where语句用于定义一个返回结果的筛选条件。每一个from语句用于产生对应的查询操作符(System.Linq.Enumerable提供的扩展方法)的结果。

let-clause ::= let itemName = selExpr

where-clause ::= where predExpr

From语句后可以跟任意多个join语句。最后一个selectgroup语句前可以放置一个orderby 语句,来对查询的结果进行排序。

join-clause ::=

join itemName in srcExpr on keyExpr equals keyExpr

 (into itemName)?

orderby-clause ::= orderby (keyExpr (ascending | descending)?) *

query-continuation ::= into itemName query-body

我们将在整个章节中使用到这些查询表达式。 当你想要检查LINQ查询句法时,请参考这个部分。

范本数据

在这里,我们需要定义一下范本数据,以便本章接下来的代码范例中使用。我们首先定义一个customer列表,同时每个customer都有Product订单。下列代码将定义这样的数据类型和初始值。

public enum Countries {

    USA,

    Italy,

}

public class Customer {

    public string Name;

    public string City;

    public Countries Country;

    public Order[] Orders;

}

public class Order {

    public int Quantity;

    public bool Shipped;

    public string Month;

    public int IdProduct;

}

public class Product {

    public int IdProduct;

    public decimal Price;

}

// -------------------------------------------------------

// Initialize a collection of customers with their orders:

// -------------------------------------------------------

customers = new Customer[] {

 new Customer {Name = "Paolo", City = "Brescia", Country = Countries.Italy, Orders =

 new Order[] {

    new Order {Quantity = 3, IdProduct = 1 , Shipped = false, Month = "January"},

    new Order {Quantity = 5, IdProduct = 2 , Shipped = true, Month = "May"}}},

 new Customer {Name = "Marco", City = "Torino", Country = Countries.Italy, Orders =

 new Order[] {

    new Order {Quantity = 10, IdProduct = 1 , Shipped = false, Month = "July"},

    new Order {Quantity = 20, IdProduct = 3 , Shipped = true, Month = "December"}}},

 new Customer {Name = "James", City = "Dallas", Country = Countries.USA, Orders =

 new Order[] {

    new Order {Quantity = 20, IdProduct = 3 , Shipped = true, Month = "December"}}},

 new Customer {Name = "Frank", City = "Seattle", Country = Countries.USA, Orders =

 new Order[] {

    new Order {Quantity = 20, IdProduct = 5 , Shipped = false, Month = "July"}}}};

products = new Product[] {

    new Product {IdProduct = 1, Price = 10 },

    new Product {IdProduct = 2, Price = 20 },

    new Product {IdProduct = 3, Price = 30 },

    new Product {IdProduct = 4, Price = 40 },

    new Product {IdProduct = 5, Price = 50 },

    new Product {IdProduct = 6, Price = 60 }};