对查询结果进行分组

分组是 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
*/

请参阅