一、LINQ查询的数据源
从应用程序的角度来看,原始源数据的特定类型和结构并不重要。 应用程序始终将源数据视为 IEnumerable<T> 或 IQueryable<T> 集合。
例如在 LINQ to XML 中,源数据显示为 IEnumerable
<XElement>。所以要对某个对象应用LINQ查询,该对象必须实现 IEnumerable<T> 或 IQueryable<T> 。
其中,数组类型并为实现 IEnumerable<T> 或 IQueryable<T> ,为什么也可进行LINQ查询呢?
二、查询可能会执行三种操作
- 检索元素的子集以生成新序列,而不修改各个元素。 查询然后可能以各种方式对返回的序列进行排序或分组,如下面的示例所示(假定
scores
是int[]
):
IEnumerable<int> highScoresQuery = from score in scores where score > 80 orderby score descending select score;
- 如前面的示例所示检索元素的序列,但是将它们转换为新类型的对象。 例如,查询可以只从数据源中的某些客户记录检索姓氏。或者可以检索完整记录,然后用于构造其他内存中对象类型甚至是 XML 数据,再生成最终的结果序列。 下面的示例演示从
int
到string
的投影。 请注意highScoresQuery
的新类型。
IEnumerable<string> highScoresQuery2 = from score in scores where score > 80 orderby score descending select $"The score is {score}";
- 检索有关源数据的单独值,如:
-
-
与特定条件匹配的元素数。
-
具有最大或最小值的元素。
-
与某个条件匹配的第一个元素,或指定元素集中特定值的总和。 例如,下面的查询从
scores
整数数组返回大于 80 的分数的数量:
-
int highScoreCount = (from score in scores where score > 80 select score) .Count();
在前面的示例中,请注意在调用 Count
方法之前,在查询表达式两边使用了括号。 也可以通过使用新变量存储具体结果,来表示此行为。 这种方法更具可读性,因为它使存储查询的变量与存储结果的查询分开。
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; int scoreCount = highScoresQuery3.Count();
三、查询表达式
查询表达式必须以 from 子句开头,且必须以 select 或 group 子句结尾。在第一个 from
子句与最后一个 select
或 group
子句之间,可以包含以下这些可选子句中的一个或多个:where、orderby、join、let,甚至是其他 from 子句。 还可以使用 into 关键字,使 join
或 group
子句的结果可以充当相同查询表达式中的其他查询子句的源。
下面的代码示例演示一个简单查询表达式,它具有一个数据源、一个筛选子句、一个排序子句并且不转换源元素。 该查询以 select
子句结尾。
static void Main() { // Data source. int[] scores = { 90, 71, 82, 93, 75, 82 }; // Query Expression. IEnumerable<int> scoreQuery = //query variable from score in scores //required where score > 80 // optional orderby score descending // optional select score; //must end with select or group // Execute the query to produce the results foreach (int testScore in scoreQuery) { Console.WriteLine(testScore); } } // Outputs: 93 90 82 82
查询变量
在上面的示例中,scoreQuery
是查询变量,它有时仅仅称为查询。查询变量可以存储采用查询语法、方法语法或是两者的组合进行表示的查询,而不是查询的结果。
开始查询表达式
查询表达式必须以 from
子句开头。 它指定数据源以及范围变量。 范围变量表示遍历源序列时,源序列中的每个连续元素。 范围变量基于数据源中元素的类型进行强类型化。 在下面的示例中,因为 countries
是 Country
对象的数组,所以范围变量也类型化为 Country
。 因为范围变量是强类型,所以可以使用点运算符访问该类型的任何可用成员。
IEnumerable<Country> countryAreaQuery = from country in countries where country.Area > 500000 //sq km select country;
范围变量一直处于范围中,直到查询使用分号或 continuation 子句退出。
查询表达式可能会包含多个 from
子句。 在源序列中的每个元素本身是集合或包含集合时,可使用其他 from
子句。 例如,假设具有 Country
对象的集合,其中每个对象都包含名为 Cities
的 City
对象集合。 若要查询每个 Country
中的 City
对象,请使用两个 from
子句,如下所示:
IEnumerable<City> cityQuery = from country in countries from city in country.Cities where city.Population > 10000 select city;
结束查询表达式
查询表达式必须以 group
子句或 select
子句结尾。
group 子句
使用 group
子句可生成按指定键组织的组的序列。 键可以是任何数据类型。 例如,下面的查询会创建包含一个或多个 Country
对象并且其键是 char
值的组的序列。
var queryCountryGroups = from country in countries group country by country.Name[0];
select 子句
使用 select
子句可生成所有其他类型的序列。 简单 select
子句只生成类型与数据源中包含的对象相同的对象的序列。 在此示例中,数据源包含 Country
对象。 orderby
子句只按新顺序对元素进行排序,而 select
子句生成重新排序的 Country
对象的序列。
IEnumerable<Country> sortedQuery = from country in countries orderby country.Area select country;
select
子句可以用于将源数据转换为新类型的序列。 此转换也称为投影。 在下面的示例中,select
子句对只包含原始元素中的字段子集的匿名类型序列进行投影。 请注意,新对象使用对象初始值设定项进行初始化。
// Here var is required because the query // produces an anonymous type. var queryNameAndPop = from country in countries select new { Name = country.Name, Pop = country.Population };
使用“into”进行延续
可以在 select
或 group
子句中使用 into
关键字创建存储查询的临时标识符。 如果在分组或选择操作之后必须对查询执行其他查询操作,则可以这样做。 在下面的示例中,countries
按 1000 万范围,根据人口进行分组。 创建这些组之后,附加子句会筛选出一些组,然后按升序对组进行排序。 若要执行这些附加操作,需要由 countryGroup
表示的延续。
// percentileQuery is an IEnumerable<IGrouping<int, Country>> var percentileQuery = from country in countries let percentile = (int) country.Population / 10_000_000 group country by percentile into countryGroup where countryGroup.Key >= 20 orderby countryGroup.Key select countryGroup; // grouping is an IGrouping<int, Country> foreach (var grouping in percentileQuery) { Console.WriteLine(grouping.Key); foreach (var country in grouping) Console.WriteLine(country.Name + ":" + country.Population); }
筛选、排序和联接
在开头 from
子句与结尾 select
或 group
子句之间,所有其他子句(where
、join
、orderby
、from
、let
)都是可选的。任何可选子句都可以在查询正文中使用零次或多次。
where 子句
使用 where
子句可基于一个或多个谓词表达式,从源数据中筛选出元素。 以下示例中的 where
子句具有一个谓词及两个条件。
IEnumerable<City> queryCityPop = from city in cities where city.Population < 200000 && city.Population > 100000 select city;
orderby 子句
使用 orderby
子句可按升序或降序对结果进行排序。 还可以指定次要排序顺序。 下面的示例使用 Area
属性对 country
对象执行主要排序。 然后使用 Population
属性执行次要排序。
IEnumerable<Country> querySortedCountries = from country in countries orderby country.Area, country.Population descending select country;
ascending
关键字是可选的;如果未指定任何顺序,则它是默认排序顺序。
join 子句
使用 join
子句可基于每个元素中指定的键之间的相等比较,将一个数据源中的元素与另一个数据源中的元素进行关联和/或合并。在 LINQ 中,联接操作是对元素属于不同类型的对象序列执行。 联接了两个序列之后,必须使用 select
或 group
语句指定要存储在输出序列中的元素。 还可以使用匿名类型将每组关联元素中的属性合并到输出序列的新类型中。 下面的示例关联其 Category
属性与 categories
字符串数组中一个类别匹配的 prod
对象。 筛选出其 Category
不与 categories
中的任何字符串匹配的产品。select
语句会投影其属性取自 cat
和 prod
的新类型。
var categoryQuery = from cat in categories join prod in products on cat equals prod.Category select new { Category = cat, Name = prod.Name };
还可以通过使用 into 关键字将 join
操作的结果存储到临时变量中来执行分组联接。
let 子句
使用 let
子句可将表达式(如方法调用)的结果存储在新范围变量中。 在下面的示例中,范围变量 firstName
存储 Split
返回的字符串数组的第一个元素。
string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" }; IEnumerable<string> queryFirstNames = from name in names let firstName = name.Split(' ')[0] select firstName; foreach (string s in queryFirstNames) Console.Write(s + " "); //Output: Svetlana Claire Sven Cesar
查询表达式中的子查询
查询子句本身可能包含查询表达式,这有时称为子查询。 每个子查询都以自己的 from
子句开头,该子句不一定指向第一个 from
子句中的相同数据源。 例如,下面的查询演示在 select 语句用于检索分组操作结果的查询表达式。
var queryGroupMax = from student in students group student by student.GradeLevel into studentGroup select new { Level = studentGroup.Key, HighestScore = (from student2 in studentGroup select student2.Scores.Average()) .Max() };