标准查询运算符是构成 LINQ 模式的关键字和方法。 C# 语言定义用于最常见查询表达式的 LINQ 查询关键字 。 编译器使用这些关键字将表达式转换为等效的方法调用。 这两种形式是同义词。 属于 System.Linq 命名空间的其他方法没有等效的查询关键字。 在这些情况下,必须使用方法语法。 本部分介绍所有查询运算符关键字。 运行时和其他 NuGet 包添加了更多旨在处理每个版本的 LINQ 查询的方法。 本部分介绍了最常见的方法,包括具有查询关键字等效项的方法。 有关 .NET 运行时支持的查询方法的完整列表,请参阅 System.Linq.Enumerable API 文档。 除了此处介绍的方法之外,此类还包含用于连接数据源、计算数据源中的单个值的方法,例如总和、平均值或其他值。
重要
这些示例使用 System.Collections.Generic.IEnumerable<T> 数据源。 基于System.Linq.IQueryProvider的数据源使用System.Linq.IQueryable<T> 数据源和表达式树。 表达式树对允许的 C# 语法有 限制 。 此外,每个 IQueryProvider
数据源(如 EF Core )可能会施加更多的限制。 查看数据源的文档。
其中大多数方法对序列进行作,其中序列是一个对象,其类型实现 IEnumerable<T> 接口或 IQueryable<T> 接口。 标准查询运算符提供查询功能,包括筛选、投影、聚合、排序等。 构成每个集的方法分别是 Enumerable 和 Queryable 类的静态成员。 它们被定义为它们所操作的类型的扩展方法。
序列 IEnumerable<T> 和 IQueryable<T> 之间的区别决定了查询在运行时的执行方式。
对于 IEnumerable<T>
,返回的可枚举对象捕获传递给该方法的参数。 枚举该对象时,会使用查询运算符的逻辑并返回查询结果。
对于IQueryable<T>
,查询被转换为表达式树。 当数据源可以优化查询时,表达式树可以转换为本机查询。
实体框架等库将 LINQ 查询转换为在数据库中执行的本机 SQL 查询。
下面的代码示例演示如何使用标准查询运算符获取有关序列的信息。
string sentence = "the quick brown fox jumps over the lazy dog";
// Split the string into individual words to create a collection.
string[] words = sentence.Split(' ');
// Using query expression syntax.
var query = from word in words
group word.ToUpper() by word.Length into gr
orderby gr.Key
select new { Length = gr.Key, Words = gr };
// Using method-based query syntax.
var query2 = words.
GroupBy(w => w.Length, w => w.ToUpper()).
Select(g => new { Length = g.Key, Words = g }).
OrderBy(o => o.Length);
foreach (var obj in query)
{
Console.WriteLine($"Words of length {obj.Length}:");
foreach (string word in obj.Words)
Console.WriteLine(word);
}
// This code example produces the following output:
//
// Words of length 3:
// THE
// FOX
// THE
// DOG
// Words of length 4:
// OVER
// LAZY
// Words of length 5:
// QUICK
// BROWN
// JUMPS
如果可能,本节中的查询将使用单词或数字序列作为输入源。 对于使用对象间关系更复杂的查询,使用以下为学校建模的源:
public enum GradeLevel
{
FirstYear = 1,
SecondYear,
ThirdYear,
FourthYear
};
public class Student
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required int ID { get; init; }
public required GradeLevel Year { get; init; }
public required List<int> Scores { get; init; }
public required int DepartmentID { get; init; }
}
public class Teacher
{
public required string First { get; init; }
public required string Last { get; init; }
public required int ID { get; init; }
public required string City { get; init; }
}
public class Department
{
public required string Name { get; init; }
public int ID { get; init; }
public required int TeacherID { get; init; }
}
每个 Student
都有一个年级、一个主要部门和一系列分数。 A Teacher
还有一个 City
属性,用于标识教师授课的校园。
Department
有一个名称,以及对担任院系主任的 Teacher
的引用。
可以在 源存储库中找到数据集。
查询运算符的类型
标准查询运算符在执行时机上有所不同,具体取决于它们是返回单个值还是值序列。 返回单例值的方法(如 Average 和 Sum)会立即执行。 返回序列的方法延迟查询执行并返回可枚举对象。 可以将一个查询的输出序列用作另一个查询的输入序列。 对查询方法的调用可以在一个查询中链接在一起,从而使查询变得任意复杂。
查询运算符
在 LINQ 查询中,第一步是指定数据源。 在 LINQ 查询中,子 from
句首先引入数据源(students
)和 范围变量 (student
)。
//queryAllStudents is an IEnumerable<Student>
var queryAllStudents = from student in students
select student;
范围变量类似于循环中的 foreach
迭代变量,只是查询表达式中没有发生实际迭代。 执行查询时,范围变量充当对每个连续元素的 students
引用。 由于编译器可以推断其 student
类型,因此无需显式指定。 您可以在 let
子句中新增更多范围变量。 有关详细信息,请参阅 let 子句。
注释
对于非泛型数据源,例如 ArrayList,必须显式键入范围变量。 有关详细信息,请参阅如何使用 LINQ (C#) 和 From 子句查询 ArrayList。
获取数据源后,可以对该数据源执行任意数量的作:
查询表达式语法表
下表列出了具有等效查询表达式子句的标准查询运算符。
使用 LINQ 进行数据转换
Language-Integrated 查询(LINQ)不仅与检索数据有关。 它也是用于转换数据的强大工具。 通过使用 LINQ 查询,可以使用源序列作为输入,并通过多种方式对其进行修改以创建新的输出序列。 可以通过排序和分组来修改序列本身,而无需修改元素本身。 但也许 LINQ 查询最强大的功能是能够创建新类型。 select 子句从输入元素创建输出元素。 使用它将输入元素转换为输出元素:
- 将多个输入序列合并到具有新类型的单个输出序列中。
- 创建其元素仅包含源序列中每个元素的一个或多个属性的输出序列。
- 创建输出序列,其元素包含对源数据执行的作的结果。
- 创建采用不同格式的输出序列。 例如,可以将 SQL 行或文本文件中的数据转换为 XML。
可以在同一查询中以各种方式组合这些转换。 此外,一个查询的输出序列可用作新查询的输入序列。 以下示例将内存中数据结构中的对象转换为 XML 元素。
// Create the query.
var studentsToXML = new XElement("Root",
from student in students
let scores = string.Join(",", student.Scores)
select new XElement("student",
new XElement("First", student.FirstName),
new XElement("Last", student.LastName),
new XElement("Scores", scores)
) // end "student"
); // end "Root"
// Execute the query.
Console.WriteLine(studentsToXML);
该代码生成以下 XML 输出:
<Root>
<student>
<First>Svetlana</First>
<Last>Omelchenko</Last>
<Scores>97,90,73,54</Scores>
</student>
<student>
<First>Claire</First>
<Last>O'Donnell</Last>
<Scores>56,78,95,95</Scores>
</student>
...
<student>
<First>Max</First>
<Last>Lindgren</Last>
<Scores>86,88,96,63</Scores>
</student>
<student>
<First>Arina</First>
<Last>Ivanova</Last>
<Scores>93,63,70,80</Scores>
</student>
</Root>
有关详细信息,请参阅在 C# 中创建 XML 树(LINQ to XML)。
可以将一个查询的结果用作后续查询的数据源。 此示例演示如何对联接作的结果进行排序。 此查询创建组联接,然后根据仍在范围内的类别元素对组进行排序。 在匿名类型初始化器中,子查询对产品序列中的所有匹配元素进行排序。
var orderedQuery = from department in departments
join student in students on department.ID equals student.DepartmentID into studentGroup
orderby department.Name
select new
{
DepartmentName = department.Name,
Students = from student in studentGroup
orderby student.LastName
select student
};
foreach (var departmentList in orderedQuery)
{
Console.WriteLine(departmentList.DepartmentName);
foreach (var student in departmentList.Students)
{
Console.WriteLine($" {student.LastName,-10} {student.FirstName,-10}");
}
}
/* Output:
Chemistry
Balzan Josephine
Fakhouri Fadi
Popov Innocenty
Seleznyova Sofiya
Vella Carmen
Economics
Adams Terry
Adaobi Izuchukwu
Berggren Jeanette
Garcia Cesar
Ifeoma Nwanneka
Jamuike Ifeanacho
Larsson Naima
Svensson Noel
Ugomma Ifunanya
Engineering
Axelsson Erik
Berg Veronika
Engström Nancy
Hicks Cassie
Keever Bruce
Micallef Nicholas
Mortensen Sven
Nilsson Erna
Tucker Michael
Yermolayeva Anna
English
Andersson Sarah
Feng Hanying
Ivanova Arina
Jakobsson Jesper
Jensen Christiane
Johansson Mark
Kolpakova Nadezhda
Omelchenko Svetlana
Urquhart Donald
Mathematics
Frost Gaby
Garcia Hugo
Hedlund Anna
Kovaleva Katerina
Lindgren Max
Maslova Evgeniya
Olsson Ruth
Sammut Maria
Sazonova Anastasiya
Physics
Åkesson Sami
Edwards Amy E.
Falzon John
Garcia Debra
Hansson Sanna
Mattsson Martina
Richardson Don
Zabokritski Eugene
*/
以下代码显示了使用方法语法的等效查询:
var orderedQuery = departments
.GroupJoin(students, department => department.ID, student => student.DepartmentID,
(department, studentGroup) => new
{
DepartmentName = department.Name,
Students = studentGroup.OrderBy(student => student.LastName)
})
.OrderBy(department => department.DepartmentName);
foreach (var departmentList in orderedQuery)
{
Console.WriteLine(departmentList.DepartmentName);
foreach (var student in departmentList.Students)
{
Console.WriteLine($" {student.LastName,-10} {student.FirstName,-10}");
}
}
尽管可以在联接前对一个或多个源序列使用 orderby
子句,但通常我们不推荐这样做。 某些 LINQ 提供程序可能不会在联接后保留该排序。 有关详细信息,请参阅 join 子句。