对查询结果进行分组
分组是 LINQ 最强大的功能之一。 以下示例演示如何以各种方式对数据进行分组:
依据单个属性。
依据字符串属性的首字母。
依据计算出的数值范围。
依据布尔谓词或其他表达式。
依据组合键。
此外,最后两个查询将其结果投影到一个新的匿名类型中,该类型仅包含学生的名字和姓氏。 有关详细信息,请参阅 group 子句。
备注
本主题中的示例使用查询对象集合中的示例代码中的 Student
类和 students
列表。
按单个属性分组示例
以下示例演示如何通过使用元素的单个属性作为分组键对源元素进行分组。 在此示例中,键是 string
,即学生的姓氏。 还可以将子字符串用于键;请参阅下一个示例。 分组操作对该类型使用默认的相等比较器。
// Variable groupByLastNamesQuery is an IEnumerable<IGrouping<string,
// DataClass.Student>>.
var groupByLastNamesQuery =
from student in students
group student by student.LastName into newGroup
orderby newGroup.Key
select newGroup;
foreach (var nameGroup in groupByLastNamesQuery)
{
Console.WriteLine($"Key: {nameGroup.Key}");
foreach (var student in nameGroup)
{
Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
}
}
/* Output:
Key: Adams
Adams, Terry
Key: Fakhouri
Fakhouri, Fadi
Key: Feng
Feng, Hanying
Key: Garcia
Garcia, Cesar
Garcia, Debra
Garcia, Hugo
Key: Mortensen
Mortensen, Sven
Key: O'Donnell
O'Donnell, Claire
Key: Omelchenko
Omelchenko, Svetlana
Key: Tucker
Tucker, Lance
Tucker, Michael
Key: Zabokritski
Zabokritski, Eugene
*/
按值分组示例
下例演示如何通过使用除对象属性以外的某个项作为分组键对源元素进行分组。 在此示例中,键是学生姓氏的第一个字母。
var groupByFirstLetterQuery =
from student in students
group student by student.LastName[0];
foreach (var studentGroup in groupByFirstLetterQuery)
{
Console.WriteLine($"Key: {studentGroup.Key}");
// Nested foreach is required to access group items.
foreach (var student in studentGroup)
{
Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
}
}
/* Output:
Key: A
Adams, Terry
Key: F
Fakhouri, Fadi
Feng, Hanying
Key: G
Garcia, Cesar
Garcia, Debra
Garcia, Hugo
Key: M
Mortensen, Sven
Key: O
O'Donnell, Claire
Omelchenko, Svetlana
Key: T
Tucker, Lance
Tucker, Michael
Key: Z
Zabokritski, Eugene
*/
按范围分组示例
以下示例演示如何通过使用某个数值范围作为分组键对源元素进行分组。 然后,查询将结果投影到一个匿名类型中,该类型仅包含学生的名字和姓氏以及该学生所属的百分点范围。 使用匿名类型的原因是没有必要使用完整的 Student
对象来显示结果。 GetPercentile
是一个帮助程序函数,它根据学生的平均分数计算百分比。 该方法返回 0 到 10 之间的整数。
int GetPercentile(Student s)
{
double avg = s.ExamScores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}
var groupByPercentileQuery =
from student in students
let percentile = GetPercentile(student)
group new
{
student.FirstName,
student.LastName
} by percentile into percentGroup
orderby percentGroup.Key
select percentGroup;
// Nested foreach required to iterate over groups and group items.
foreach (var studentGroup in groupByPercentileQuery)
{
Console.WriteLine($"Key: {studentGroup.Key * 10}");
foreach (var item in studentGroup)
{
Console.WriteLine($"\t{item.LastName}, {item.FirstName}");
}
}
/* Output:
Key: 60
Garcia, Debra
Key: 70
O'Donnell, Claire
Key: 80
Adams, Terry
Feng, Hanying
Garcia, Cesar
Garcia, Hugo
Mortensen, Sven
Omelchenko, Svetlana
Tucker, Lance
Zabokritski, Eugene
Key: 90
Fakhouri, Fadi
Tucker, Michael
*/
按比较分组示例
以下示例演示如何通过使用布尔比较表达式对源元素进行分组。 在此示例中,布尔表达式会测试学生的平均考试分数是否超过 75。 与上述示例一样,结果被投影到一个匿名类型中,因为不需要完整的源元素。 请注意,执行查询时,该匿名类型中的属性会变成 Key
成员上的属性,并且可以通过名称进行访问。
var groupByHighAverageQuery =
from student in students
group new
{
student.FirstName,
student.LastName
} by student.ExamScores.Average() > 75 into studentGroup
select studentGroup;
foreach (var studentGroup in groupByHighAverageQuery)
{
Console.WriteLine($"Key: {studentGroup.Key}");
foreach (var student in studentGroup)
{
Console.WriteLine($"\t{student.FirstName} {student.LastName}");
}
}
/* Output:
Key: True
Terry Adams
Fadi Fakhouri
Hanying Feng
Cesar Garcia
Hugo Garcia
Sven Mortensen
Svetlana Omelchenko
Lance Tucker
Michael Tucker
Eugene Zabokritski
Key: False
Debra Garcia
Claire O'Donnell
*/
按匿名类型分组
以下示例演示如何使用匿名类型来封装包含多个值的键。 在此示例中,第一个键值是学生姓氏的第一个字母。 第二个键值是一个布尔值,指定该学生在第一次考试中的得分是否超过了 85。 可以按照该键中的任何属性对组进行排序。
var groupByCompoundKey =
from student in students
group student by new
{
FirstLetter = student.LastName[0],
IsScoreOver85 = student.ExamScores[0] > 85
} into studentGroup
orderby studentGroup.Key.FirstLetter
select studentGroup;
foreach (var scoreGroup in groupByCompoundKey)
{
string s = scoreGroup.Key.IsScoreOver85 == true ? "more than 85" : "less than 85";
Console.WriteLine($"Name starts with {scoreGroup.Key.FirstLetter} who scored {s}");
foreach (var item in scoreGroup)
{
Console.WriteLine($"\t{item.FirstName} {item.LastName}");
}
}
/* Output:
Name starts with A who scored more than 85
Terry Adams
Name starts with F who scored more than 85
Fadi Fakhouri
Hanying Feng
Name starts with G who scored more than 85
Cesar Garcia
Hugo Garcia
Name starts with G who scored less than 85
Debra Garcia
Name starts with M who scored more than 85
Sven Mortensen
Name starts with O who scored less than 85
Claire O'Donnell
Name starts with O who scored more than 85
Svetlana Omelchenko
Name starts with T who scored less than 85
Lance Tucker
Name starts with T who scored more than 85
Michael Tucker
Name starts with Z who scored more than 85
Eugene Zabokritski
*/