LINQ 查询简介 (C#)

查询是一种从数据源检索数据的表达式。 不同的数据源具有不同的原生查询语言,例如,用于关系数据库的 SQL 和用于 XML 的 XQuery。 开发人员对于他们必须支持的每种数据源或数据格式,都必须学习一种新的查询语言。 LINQ 通过为各种数据源和数据格式提供一致的 C# 语言模型,简化了这一情况。 在 LINQ 查询中,你始终使用 C# 对象。 当有 LINQ 提供程序可用时,你可以使用相同的基本编码模式来查询和转换 XML 文档、SQL 数据库、.NET 集合中的数据以及任何其他格式的数据。

查询操作的三个部分

所有 LINQ 查询操作都由以下三个不同的操作组成:

  1. 获取数据源。
  2. 创建查询。
  3. 执行查询。

下面的示例演示如何用源代码表示查询操作的三个部分。 为方便起见,此示例将一个整数数组用作数据源;但其中涉及的概念同样适用于其他数据源。 本文的其余部分也引用了此示例。

// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = [ 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);
}

下图演示完整的查询操作。 在 LINQ 中,查询的执行不同于查询本身。 换句话说,你不会通过创建查询变量来检索任何数据。

Diagram of the complete LINQ query operation.

数据源

上例中的数据源是一个数组,它支持泛型 IEnumerable<T> 接口。 这一事实意味着该数据源可以用 LINQ 进行查询。 查询在 foreach 语句中执行,且 foreach 需要 IEnumerableIEnumerable<T>。 支持 IEnumerable<T> 或派生接口(如泛型 IQueryable<T>)的类型称为可查询类型 。

可查询类型不需要进行修改或特殊处理就可以用作 LINQ 数据源。 如果源数据还没有作为可查询类型出现在内存中,则 LINQ 提供程序必须以此方式表示源数据。 例如,LINQ to XML 将 XML 文档加载到可查询的 XElement 类型中:

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

使用 EntityFramework,你在 C# 类与数据库架构之间创建对象关系映射。 你针对这些对象编写查询,然后 EntityFramework 在运行时处理与数据库的通信。 下例中,Customers 表示数据库中的特定表,而查询结果的类型 IQueryable<T> 派生自 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 提供程序的文档。 但基本规则很简单:LINQ 数据源是支持泛型 IEnumerable<T> 接口(或者是继承该接口的接口,通常表示为 IQueryable<T>)的任何对象。

注意

支持非泛型 IEnumerable 接口的类型(如 ArrayList)还可用作 LINQ 数据源。 有关详细信息,请参阅如何使用 LINQ 查询 ArrayList (C#)

查询

查询指定要从数据源中检索的信息。 查询还可以指定在返回这些信息之前如何对其进行排序、分组和结构化。 查询存储在查询变量中,并用查询表达式进行初始化。 你使用 C# 查询语法来编写查询。

上一个示例中的查询从整数数组中返回所有偶数。 该查询表达式包含三个子句:fromwhereselect。 (如果你熟悉 SQL,你会注意到这些子句的顺序与 SQL 中的顺序相反。)from 子句指定数据源,where 子句应用筛选器,select 子句指定返回的元素的类型。 本部分详细介绍了所有查询子句。 目前需要注意的是,在 LINQ 中,查询变量本身不执行任何操作并且不返回任何数据。 它只是存储在以后某个时刻执行查询时为生成结果而必需的信息。 有关如何构造查询的详细信息,请参阅标准查询运算符概述 (C#)

注意

还可以使用方法语法来表示查询。 有关详细信息,请参阅 LINQ 中的查询语法和方法语法

查询执行

延迟执行

查询变量本身只存储查询命令。 查询的实际执行将推迟到在 foreach 语句中循环访问查询变量之后进行。 此概念称为延迟执行,下面的示例对此进行了演示:

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

foreach 语句也是检索查询结果的地方。 例如,在上一个查询中,迭代变量 num 保存了返回的序列中的每个值(一次保存一个值)。

由于查询变量本身从不保存查询结果,因此你可以重复执行它来检索更新的数据。 例如,你可能有一个正由单独的应用程序不断进行更新的数据库。 在应用程序中,你可以创建一个检索最新数据的查询,并可以不时地执行该查询以便检索更新的结果。

强制立即执行

对一系列源元素执行聚合函数的查询必须首先循环访问这些元素。 CountMaxAverageFirst 就属于此类查询。 由于查询本身必须使用 foreach 来返回结果,因此这些方法在执行时不使用显式 foreach 语句。 这些查询返回单个值,而不是 IEnumerable 集合。 下面的查询返回源数组中偶数的计数:

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

int evenNumCount = evenNumQuery.Count();

要强制立即执行任何查询并缓存其结果,可调用 ToListToArray 方法。

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 循环来强制执行查询。 但是,通过调用 ToListToArray,也可以将所有数据缓存在单个集合对象中。

另请参阅