编写第一个 LINQ 查询 (Visual Basic)

查询是一种从数据源检索数据的表达式。 查询以专用查询语言表示。 随着时间的推移,人们已经为各种类型的数据源开发了不同的语言;例如,用于关系数据库的 SQL 和用于 XML 的 XQuery。 这使得应用程序开发人员需要为支持的每种数据源或数据格式学习新的查询语言。

语言集成查询 (LINQ) 通过提供处理各种数据源和数据格式的数据的一致模型,简化了这一情况。 在 LINQ 查询中,始终会用到对象。 可以使用相同的基本编码模式来查询和转换 XML 文档、SQL 数据库、ADO.NET 数据集和实体、.NET Framework 集合以及可使用 LINQ 提供程序的任何其他数据源或数据格式。 本文档介绍了创建和使用基本 LINQ 查询的三个阶段。

查询操作的三个阶段

LINQ 查询操作由以下三个操作组成:

  1. 获取数据源。

  2. 创建查询。

  3. 执行查询。

在 LINQ 中,执行查询完全不同于创建查询。 仅仅通过创建查询并不能检索任何数据。 这一点将在本主题后面部分进行更详细的讨论。

下面的示例演示了查询操作的三个部分。 为了便于演示,该示例使用整数数组作为方便的数据源。 但是,同样的概念也适用于其他数据源。

注意

“编译”页的“项目设计器 (Visual Basic)”中,确保“Option infer”设置为“打开”。

' Data source.
Dim numbers() As Integer = {0, 1, 2, 3, 4, 5, 6}

' Query creation.
Dim evensQuery = From num In numbers
                 Where num Mod 2 = 0
                 Select num

' Query execution.
For Each number In evensQuery
    Console.Write(number & " ")
Next

输出:

0 2 4 6

数据源

由于上例中的数据源是一个数组,因此它隐式支持泛型 IEnumerable<T> 接口。 正是这一事实使你能够将数组用作 LINQ 查询的数据源。 支持 IEnumerable<T> 或派生接口(如泛型 IQueryable<T>)的类型称为可查询类型 。

作为隐式可查询类型,数组不需要进行修改或特殊处理即可用作 LINQ 数据源。 对于支持 IEnumerable<T> 的任何集合类型(包括泛型 List<T>Dictionary<TKey,TValue> 和 NET Framework 类库中的其他类),都是如此。

如果源数据尚未实现 IEnumerable<T>,则需要使用 LINQ 提供程序为该数据源实现标准查询运算符的功能。 例如,LINQ to XML 可处理将 XML 文档载入可查询 XElement 类型中的工作,如下例所示。 有关标准查询运算符的详细信息,请参阅标准查询运算符概述 (Visual Basic)

' Create a data source from an XML document.
Dim contacts = XElement.Load("c:\myContactList.xml")

借助 LINQ to SQL,首先在 Visual Studio 中手动或使用 Visual Studio 中的 LINQ to SQL 工具在设计时创建对象关系映射。 针对这些对象编写查询,然后由 LINQ to SQL 在运行时处理与数据库的通信。 在下面的示例中,customers 表示数据库中的特定表,并且 Table<TEntity> 支持泛型 IQueryable<T>

' Create a data source from a SQL table.
Dim db As New DataContext("C:\Northwind\Northwnd.mdf")
Dim customers As Table(Of Customer) = db.GetTable(Of Customer)

有关如何创建特定类型的数据源的详细信息,请参阅各种 LINQ 提供程序的文档。 (有关这些提供程序的列表,请参阅 LINQ (语言集成查询)。) 但基本规则很简单:LINQ 数据源可以是支持泛型 IEnumerable<T> 接口或从中继承的接口的任意对象。

注意

支持非泛型 IEnumerable 接口的类型(如 ArrayList)也可用作 LINQ 数据源。 有关使用 ArrayList 的示例,请参阅如何:使用 LINQ 查询 ArrayList (Visual Basic)

查询

在查询中,可以指定要从一个或多个数据源检索哪些信息。 还可以选择指定在返回信息之前应该如何对信息进行排序、分组或结构化。 为了创建查询,Visual Basic 已将新的查询语法合并到了语言中。

执行时,以下示例中的查询将返回整数数组 numbers 的所有偶数。

' Data source.
Dim numbers() As Integer = {0, 1, 2, 3, 4, 5, 6}

' Query creation.
Dim evensQuery = From num In numbers
                 Where num Mod 2 = 0
                 Select num

' Query execution.
For Each number In evensQuery
    Console.Write(number & " ")
Next

该查询表达式包含三个子句:FromWhereSelectBasic Query Operations (Visual Basic) 中讨论了每个查询表达式子句的具体功能和用途。 有关详细信息,请参阅查询。 请注意,在 LINQ 中,查询定义通常存储在变量中,并在稍后执行。 查询变量(如上例中的 evensQuery)必须是可查询类型。 evensQuery 的类型为 IEnumerable(Of Integer),由编译器使用本地类型推理进行赋值。

必须记住,查询变量本身不执行任何操作,也不会返回任何数据。 它只存储查询定义。 在上一示例中,它是执行查询的 For Each 循环。

查询执行

查询执行与查询创建相互独立。 查询创建可定义查询,但执行由其他机制触发。 可以在定义查询后立即执行查询(立即执行),或者可以先存储定义,稍后再执行查询(延迟执行)。

延迟执行

典型的 LINQ 查询类似于上一示例中定义的 evensQuery。 它创建查询,但不立即执行。 相反,查询定义存储在查询变量 evensQuery 中。 你可以稍后执行查询,通常是使用 For Each 循环(将返回值序列),或者应用标准查询运算符(如 CountMax)。 此过程称为“延迟执行”。

' Query execution that results in a sequence of values.
For Each number In evensQuery
    Console.Write(number & " ")
Next

' Query execution that results in a single value.
Dim evens = evensQuery.Count()

对于值序列,可以使用 For Each 循环中的迭代变量(在上一示例中为 number)来访问检索的数据。 由于查询变量 evensQuery 保存查询定义而不是查询结果,因此,可以通过多次使用查询变量随时执行查询。 例如,你可能有一个正由单独的应用程序持续更新的数据库。 创建从该数据库检索数据的查询后,可以使用 For Each 循环反复执行查询,每次都检索最新的数据。

下面的示例演示了延迟执行的工作原理。 在定义 evensQuery2 并使用 For Each 循环执行之后(如前面的示例所示),数据源 numbers 中的某些元素会发生更改。 然后第二个 For Each 循环将再次运行 evensQuery2。 第二次的结果是不同的,因为 For Each 循环使用 numbers 中的新值再次执行查询。

Dim numberArray() = {0, 1, 2, 3, 4, 5, 6}

Dim evensQuery2 = From num In numberArray
                  Where num Mod 2 = 0
                  Select num

Console.WriteLine("Evens in original array:")
For Each number In evensQuery2
    Console.Write("  " & number)
Next
Console.WriteLine()

' Change a few array elements.
numberArray(1) = 10
numberArray(4) = 22
numberArray(6) = 8

' Run the same query again.
Console.WriteLine(vbCrLf & "Evens in changed array:")
For Each number In evensQuery2
    Console.Write("  " & number)
Next
Console.WriteLine()

输出:

Evens in original array:

0 2 4 6

Evens in changed array:

0 10 2 22 8

立即执行

在延迟执行查询时,查询定义存储在查询变量中,供以后执行。 在立即执行中,查询在定义时执行。 应用需要访问查询结果的各个元素的方法时,将触发执行。 通常使用返回单个值的标准查询运算符之一来强制立即执行。 例如:CountMaxAverageFirst。 这些标准查询运算符在应用查询后即执行查询,以便计算并返回单一结果。 有关返回单个值的标准查询运算符的详细信息,请参阅聚合运算元素运算限定符运算

下面的查询将返回整数数组中的偶数计数。 查询定义未保存,且 numEvens 是一个简单的 Integer

Dim numEvens = (From num In numbers
                Where num Mod 2 = 0
                Select num).Count()

可以使用 Aggregate 方法实现相同的结果。

Dim numEvensAgg = Aggregate num In numbers
                  Where num Mod 2 = 0
                  Select num
                  Into Count()

还可以对查询(立即执行)或查询变量(延迟查询)调用 ToListToArray 方法来强制执行查询,如以下代码所示。

' Immediate execution.
Dim evensList = (From num In numbers
                 Where num Mod 2 = 0
                 Select num).ToList()

' Deferred execution.
Dim evensQuery3 = From num In numbers
                  Where num Mod 2 = 0
                  Select num
' . . .
Dim evensArray = evensQuery3.ToArray()

在以上示例中,evensQuery3 是查询变量,但 evensList 是列表,evensArray 是数组。

在你希望立即执行查询并将结果缓存在单个集合对象中的情况下,使用 ToListToArray 强制立即执行查询特别有用。 有关这些方法的详细信息,请参阅转换数据类型

还可使用 IEnumerable 方法(如 IEnumerable.GetEnumerator 方法)执行查询。

另请参阅