C 中的 LINQ 查询简介#

查询是从数据源检索数据的表达式。 不同的数据源具有不同的本机查询语言,例如用于关系数据库的 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 中,查询的执行与查询本身不同。 换句话说,不会通过创建查询变量来检索任何数据。

完整的 LINQ 查询操作示意图。

数据源

前面的示例中的数据源是一个支持泛型 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>通常继承自它的接口。

注释

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

查询

查询指定要从数据源或源检索的信息。 (可选)查询还指定在返回之前应如何对信息进行排序、分组和调整。 查询存储在查询变量中,并使用查询表达式进行初始化。 使用 C# 查询语法 编写查询。

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

注释

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

按执行方式对标准查询运算符进行分类

标准查询运算符方法的 LINQ to Objects 实现以两种主要方式之一执行: 立即延迟。 使用延迟执行的查询运算符可以另外分为两个类别: 流式处理 和非 流式处理

立刻

立即执行意味着将读取数据源,并执行一次操作。 所有返回标量结果的标准查询运算符均立即执行。 此类查询的示例包括CountMaxAverageFirst。 这些方法在没有显式 foreach 语句的情况下执行,因为查询本身必须使用 foreach 才能返回结果。 这些查询返回单个值,而不是 IEnumerable 集合。 可以强制任何查询通过Enumerable.ToListEnumerable.ToArray方法立即执行。 立即执行可重用查询结果,而不是查询声明。 检索结果一次,然后存储以供将来使用。 下面的查询返回源数组中偶数的计数:

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 缓存单个集合对象中的所有数据。

已推迟

延迟执行意味着操作不会在查询声明所在的代码点执行。 仅当枚举查询变量时,才执行该作,例如使用 foreach 语句。 执行查询的结果取决于执行查询时数据源的内容,而不是定义查询的时间。 如果多次枚举查询变量,则每次结果都可能会有所不同。 几乎所有返回类型为 IEnumerable<T>IOrderedEnumerable<TElement> 的标准查询运算符都是以延迟方式执行的。 延迟执行提供了查询重用功能,因为每次循环访问查询结果时,查询都会从数据源中提取更新的数据。 以下代码演示延迟执行的示例:

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

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

由于查询变量本身永远不会保存查询结果,因此可以重复执行它来检索更新的数据。 例如,单独的应用程序可能会不断更新数据库。 在 应用程序中 ,可以创建一个检索最新数据的查询,并且可以按时间间隔执行它来检索更新的结果。

使用延迟执行的查询运算符可以另外分类为流式处理或非流式处理。

流媒体

流式处理运算符不需要在生成元素前读取所有源数据。 在执行时,流式运算符在读取每个源元素时执行其操作,并在适当时产出元素。 流式处理运算符继续读取源元素,直到可以生成结果元素。 这意味着可以读取多个源元素以生成一个结果元素。

非流式处理

非流式处理运算符必须读取所有源数据,然后才能生成结果元素。 排序或分组等作属于此类别。 在执行时,非流式处理查询运算符读取所有源数据,将其放入数据结构中,执行作并生成生成的元素。

分类表

下表根据其执行方法对每个标准查询运算符方法进行分类。

注释

如果运算符在两列中标记,则作中涉及两个输入序列,并且每个序列的计算方式不同。 在这些情况下,它始终是参数列表中以延迟流式处理方式计算的第一个序列。

标准查询运算符 返回类型 立即执行 延迟的流式处理执行 延迟非流式处理执行
Aggregate TSource
All Boolean
Any Boolean
AsEnumerable IEnumerable<T>
Average 单个数值
Cast IEnumerable<T>
Concat IEnumerable<T>
Contains Boolean
Count Int32
DefaultIfEmpty IEnumerable<T>
Distinct IEnumerable<T>
ElementAt TSource
ElementAtOrDefault TSource?
Empty IEnumerable<T>
Except IEnumerable<T>
First TSource
FirstOrDefault TSource?
GroupBy IEnumerable<T>
GroupJoin IEnumerable<T>
Intersect IEnumerable<T>
Join IEnumerable<T>
Last TSource
LastOrDefault TSource?
LongCount Int64
Max 单个数值, TSourceTResult?
Min 单个数值, TSourceTResult?
OfType IEnumerable<T>
OrderBy IOrderedEnumerable<TElement>
OrderByDescending IOrderedEnumerable<TElement>
Range IEnumerable<T>
Repeat IEnumerable<T>
Reverse IEnumerable<T>
Select IEnumerable<T>
SelectMany IEnumerable<T>
SequenceEqual Boolean
Single TSource
SingleOrDefault TSource?
Skip IEnumerable<T>
SkipWhile IEnumerable<T>
Sum 单个数值
Take IEnumerable<T>
TakeWhile IEnumerable<T>
ThenBy IOrderedEnumerable<TElement>
ThenByDescending IOrderedEnumerable<TElement>
ToArray TSource[] 数组
ToDictionary Dictionary<TKey,TValue>
ToList IList<T>
ToLookup ILookup<TKey,TElement>
Union IEnumerable<T>
Where IEnumerable<T>

LINQ to 对象

“LINQ to Objects”是指直接将 LINQ 查询用于任何 IEnumerableIEnumerable<T> 集合。 可以使用 LINQ 查询任何可枚举集合,例如 List<T>ArrayDictionary<TKey,TValue>。 集合可以是用户定义的,也可以是由 .NET API 返回的类型。 在 LINQ 方法中,编写声明性代码来描述要检索的内容。 LINQ to Objects 为使用 LINQ 编程提供了很好的介绍。

LINQ 查询与传统 foreach 循环相比具有三大优势:

  • 它们更简洁易读,尤其是在筛选多个条件时。
  • 它们以最少的应用程序代码提供强大的筛选、排序和分组功能。
  • 它们可以移植到其他数据源,几乎无需修改。

要在数据上执行的操作越复杂,使用 LINQ 而不是传统迭代技术所带来的好处就越大。

将查询结果存储在内存中

查询基本上是有关如何检索和组织数据的一组说明。 当请求结果中的每个后续项目时,查询将延迟执行。 当使用 foreach 迭代结果时,项会按访问顺序返回。 若要评估查询并存储其结果而不执行 foreach 循环,只需对查询变量调用以下方法之一:

存储查询结果时,应将返回的集合对象分配给新变量,如以下示例所示:

List<int> numbers = [ 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 ];

IEnumerable<int> queryFactorsOfFour = from num in numbers
                                      where num % 4 == 0
                                      select num;

// Store the results in a new variable
// without executing a foreach loop.
var factorsofFourList = queryFactorsOfFour.ToList();

// Read and write from the newly created list to demonstrate that it holds data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);

另请参阅