LINQ 查询简介 (C#)Introduction to LINQ Queries (C#)

查询是一种从数据源检索数据的表达式。A query is an expression that retrieves data from a data source. 查询通常用专门的查询语言来表示。Queries are usually expressed in a specialized query language. 随着时间的推移,人们已经为各种数据源开发了不同的语言;例如,用于关系数据库的 SQL 和用于 XML 的 XQuery。Different languages have been developed over time for the various types of data sources, for example SQL for relational databases and XQuery for XML. 因此,开发人员对于他们必须支持的每种数据源或数据格式,都不得不学习一种新的查询语言。Therefore, developers have had to learn a new query language for each type of data source or data format that they must support. LINQ 通过提供处理各种数据源和数据格式的数据的一致模型,简化了这一情况。LINQ simplifies this situation by offering a consistent model for working with data across various kinds of data sources and formats. 在 LINQ 查询中,始终会用到对象。In a LINQ query, you are always working with objects. 可以使用相同的基本编码模式来查询和转换 XML 文档、SQL 数据库、ADO.NET 数据集、.NET 集合中的数据以及 LINQ 提供程序可用的任何其他格式的数据。You use the same basic coding patterns to query and transform data in XML documents, SQL databases, ADO.NET Datasets, .NET collections, and any other format for which a LINQ provider is available.

查询操作的三个部分Three Parts of a Query Operation

所有 LINQ 查询操作都由以下三个不同的操作组成:All LINQ query operations consist of three distinct actions:

  1. 获取数据源。Obtain the data source.

  2. 创建查询。Create the query.

  3. 执行查询。Execute the query.

下面的示例演示如何用源代码表示查询操作的三个部分。The following example shows how the three parts of a query operation are expressed in source code. 为方便起见,此示例将一个整数数组用作数据源;但其中涉及的概念同样适用于其他数据源。The example uses an integer array as a data source for convenience; however, the same concepts apply to other data sources also. 本主题的其余部分也会引用此示例。This example is referred to throughout the rest of this topic.

class IntroToLINQ
{        
    static void Main()
    {
        // The Three Parts of a LINQ Query:
        // 1. Data source.
        int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

        // 2. Query creation.
        // numQuery is an IEnumerable<int>
        var numQuery =
            from num in numbers
            where (num % 2) == 0
            select num;

        // 3. Query execution.
        foreach (int num in numQuery)
        {
            Console.Write("{0,1} ", num);
        }
    }
}

下图演示完整的查询操作。The following illustration shows the complete query operation. 在 LINQ 中,查询的执行不同于查询本身。In LINQ, the execution of the query is distinct from the query itself. 换句话说,仅通过创建查询变量不会检索到任何数据。In other words, you have not retrieved any data just by creating a query variable.

完整 LINQ 查询运算的图表。

数据源The Data Source

上例中,数据源是一个数组,因此它隐式支持泛型 IEnumerable<T> 接口。In the previous example, because the data source is an array, it implicitly supports the generic IEnumerable<T> interface. 这一事实意味着该数据源可以用 LINQ 进行查询。This fact means it can be queried with LINQ. 查询在 foreach 语句中执行,且 foreach 需要 IEnumerableIEnumerable<T>A query is executed in a foreach statement, and foreach requires IEnumerable or IEnumerable<T>. 支持 IEnumerable<T> 或派生接口(如泛型 IQueryable<T>)的类型称为可查询类型 。Types that support IEnumerable<T> or a derived interface such as the generic IQueryable<T> are called queryable types.

可查询类型不需要进行修改或特殊处理就可以用作 LINQ 数据源。A queryable type requires no modification or special treatment to serve as a LINQ data source. 如果源数据还没有作为可查询类型出现在内存中,则 LINQ 提供程序必须以此方式表示源数据。If the source data is not already in memory as a queryable type, the LINQ provider must represent it as such. 例如,LINQ to XMLLINQ to XML 将 XML 文档加载到可查询的 XElement 类型中:For example, LINQ to XMLLINQ to XML loads an XML document into a queryable XElement type:

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

借助 LINQ to SQLLINQ to SQL,首先在 Visual Studio 中手动或使用 Visual Studio 中的 LINQ to SQL 工具在设计时创建对象关系映射。With LINQ to SQLLINQ to SQL, you first create an object-relational mapping at design time either manually or by using the LINQ to SQL Tools in Visual Studio in Visual Studio. 针对这些对象编写查询,然后由 LINQ to SQLLINQ to SQL 在运行时处理与数据库的通信。You write your queries against the objects, and at run-time LINQ to SQLLINQ to SQL handles the communication with the database. 下例中,Customers 表示数据库中的特定表,而查询结果的类型 IQueryable<T> 派生自 IEnumerable<T>In the following example, Customers represents a specific table in the database, and the type of the query result, IQueryable<T>, derives from IEnumerable<T>.

Northwnd db = new Northwnd(@"c:\northwnd.mdf");  
  
// Query for customers in London.  
IQueryable<Customer> custQuery =  
    from cust in db.Customers  
    where cust.City == "London"  
    select cust;  

有关如何创建特定类型的数据源的详细信息,请参阅各种 LINQ 提供程序的文档。For more information about how to create specific types of data sources, see the documentation for the various LINQ providers. 但基本规则很简单:LINQ 数据源是支持泛型 IEnumerable<T> 接口或从中继承的接口的任意对象。However, the basic rule is very simple: a LINQ data source is any object that supports the generic IEnumerable<T> interface, or an interface that inherits from it.

备注

支持非泛型 IEnumerable 接口的类型(如 ArrayList)还可用作 LINQ 数据源。Types such as ArrayList that support the non-generic IEnumerable interface can also be used as a LINQ data source. 有关详细信息,请参阅如何使用 LINQ 查询 ArrayList (C#)For more information, see How to query an ArrayList with LINQ (C#).

查询The Query

查询指定要从数据源中检索的信息。The query specifies what information to retrieve from the data source or sources. 查询还可以指定在返回这些信息之前如何对其进行排序、分组和结构化。Optionally, a query also specifies how that information should be sorted, grouped, and shaped before it is returned. 查询存储在查询变量中,并用查询表达式进行初始化。A query is stored in a query variable and initialized with a query expression. 为使编写查询的工作变得更加容易,C# 引入了新的查询语法。To make it easier to write queries, C# has introduced new query syntax.

上一个示例中的查询从整数数组中返回所有偶数。The query in the previous example returns all the even numbers from the integer array. 该查询表达式包含三个子句:fromwhereselectThe query expression contains three clauses: from, where and select. (如果熟悉 SQL,会注意到这些子句的顺序与 SQL 中的顺序相反。)from 子句指定数据源,where 子句应用筛选器,select 子句指定返回的元素的类型。(If you are familiar with SQL, you will have noticed that the ordering of the clauses is reversed from the order in SQL.) The from clause specifies the data source, the where clause applies the filter, and the select clause specifies the type of the returned elements. LINQ 查询表达式一节中详细讨论了这些子句和其他查询子句。These and the other query clauses are discussed in detail in the LINQ Query Expressions section. 目前需要注意的是,在 LINQ 中,查询变量本身不执行任何操作并且不返回任何数据。For now, the important point is that in LINQ, the query variable itself takes no action and returns no data. 它只是存储在以后某个时刻执行查询时为生成结果而必需的信息。It just stores the information that is required to produce the results when the query is executed at some later point. 有关在后台如何构造查询的详细信息,请参阅标准查询运算符概述 (C#)For more information about how queries are constructed behind the scenes, see Standard Query Operators Overview (C#).

备注

还可以使用方法语法来表示查询。Queries can also be expressed by using method syntax. 有关详细信息,请参阅 LINQ 中的查询语法和方法语法For more information, see Query Syntax and Method Syntax in LINQ.

查询执行Query Execution

延迟执行Deferred Execution

如前所述,查询变量本身只存储查询命令。As stated previously, the query variable itself only stores the query commands. 查询的实际执行将推迟到在 foreach 语句中循环访问查询变量之后进行。The actual execution of the query is deferred until you iterate over the query variable in a foreach statement. 此概念称为延迟执行,下面的示例对此进行了演示:This concept is referred to as deferred execution and is demonstrated in the following example:

//  Query execution. 
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

foreach 语句也是检索查询结果的地方。The foreach statement is also where the query results are retrieved. 例如,在上一个查询中,迭代变量 num 保存了返回的序列中的每个值(一次保存一个值)。For example, in the previous query, the iteration variable num holds each value (one at a time) in the returned sequence.

由于查询变量本身从不保存查询结果,因此可以根据需要随意执行查询。Because the query variable itself never holds the query results, you can execute it as often as you like. 例如,可以通过一个单独的应用程序持续更新数据库。For example, you may have a database that is being updated continually by a separate application. 在应用程序中,可以创建一个检索最新数据的查询,并可以按某一时间间隔反复执行该查询以便每次检索不同的结果。In your application, you could create one query that retrieves the latest data, and you could execute it repeatedly at some interval to retrieve different results every time.

强制立即执行Forcing Immediate Execution

对一系列源元素执行聚合函数的查询必须首先循环访问这些元素。Queries that perform aggregation functions over a range of source elements must first iterate over those elements. CountMaxAverageFirst 就属于此类查询。Examples of such queries are Count, Max, Average, and First. 由于查询本身必须使用 foreach 以便返回结果,因此这些查询在执行时不使用显式 foreach 语句。These execute without an explicit foreach statement because the query itself must use foreach in order to return a result. 另外还要注意,这些类型的查询返回单个值,而不是 IEnumerable 集合。Note also that these types of queries return a single value, not an IEnumerable collection. 下面的查询返回源数组中偶数的计数:The following query returns a count of the even numbers in the source array:

var evenNumQuery = 
    from num in numbers
    where (num % 2) == 0
    select num;

int evenNumCount = evenNumQuery.Count();

要强制立即执行任何查询并缓存其结果,可调用 ToListToArray 方法。To force immediate execution of any query and cache its results, you can call the ToList or ToArray methods.

List<int> numQuery2 =
    (from num in numbers
     where (num % 2) == 0
     select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 =
    (from num in numbers
     where (num % 2) == 0
     select num).ToArray();

此外,还可以通过在紧跟查询表达式之后的位置放置一个 foreach 循环来强制执行查询。You can also force execution by putting the foreach loop immediately after the query expression. 但是,通过调用 ToListToArray,也可以将所有数据缓存在单个集合对象中。However, by calling ToList or ToArray you also cache all the data in a single collection object.

请参阅See also