Share via


Gegevens groeperen (C#)

Groeperen verwijst naar de werking van het indelen van gegevens in groepen, zodat de elementen in elke groep een gemeenschappelijk kenmerk delen. In de volgende afbeelding ziet u de resultaten van het groeperen van een reeks tekens. De sleutel voor elke groep is het teken.

Diagram met een LINQ-groeperingsbewerking

Belangrijk

In deze voorbeelden wordt een System.Collections.Generic.IEnumerable<T> gegevensbron gebruikt. Gegevensbronnen op System.Linq.IQueryProvider basis van het gebruik van System.Linq.IQueryable<T> gegevensbronnen en expressiestructuren. Expressiestructuren hebben beperkingen voor de toegestane C#-syntaxis. Bovendien kan elke IQueryProvider gegevensbron, zoals EF Core , meer beperkingen opleggen. Raadpleeg de documentatie voor uw gegevensbron.

De standaardqueryoperatormethoden waarmee gegevenselementen worden gegroepeerd, worden vermeld in de volgende tabel.

Methodenaam Beschrijving Syntaxis van C#-queryexpressie Meer informatie
GroupBy Hiermee worden elementen gegroepeerd die een gemeenschappelijk kenmerk delen. Een IGrouping<TKey,TElement> object vertegenwoordigt elke groep. group … by

– of –

group … by … into …
Enumerable.GroupBy

Queryable.GroupBy
ToLookup Voegt elementen in een Lookup<TKey,TElement> (een-op-veel-woordenlijst) in op basis van een sleutelkiezerfunctie. Niet van toepassing. Enumerable.ToLookup

In het volgende codevoorbeeld wordt de group by component gebruikt om gehele getallen in een lijst te groeperen op basis van of ze even of oneven zijn.

List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];

IEnumerable<IGrouping<int, int>> query = from number in numbers
                                         group number by number % 2;

foreach (var group in query)
{
    Console.WriteLine(group.Key == 0 ? "\nEven numbers:" : "\nOdd numbers:");
    foreach (int i in group)
    {
        Console.WriteLine(i);
    }
}

De equivalente query met behulp van de methodesyntaxis wordt weergegeven in de volgende code:

List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];

IEnumerable<IGrouping<int, int>> query = numbers
    .GroupBy(number => number % 2);

foreach (var group in query)
{
    Console.WriteLine(group.Key == 0 ? "\nEven numbers:" : "\nOdd numbers:");
    foreach (int i in group)
    {
        Console.WriteLine(i);
    }
}

In de volgende voorbeelden in dit artikel worden de algemene gegevensbronnen voor dit gebied gebruikt:

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; }
}

Elk Student heeft een cijferniveau, een primaire afdeling en een reeks scores. Een Teacher heeft ook een City eigenschap die de campus identificeert waar de docent klassen heeft. A Department heeft een naam en een verwijzing naar een Teacher persoon die als afdelingshoofd fungeert.

Queryresultaten groeperen

Groeperen is een van de krachtigste mogelijkheden van LINQ. In de volgende voorbeelden ziet u hoe u gegevens op verschillende manieren kunt groeperen:

  • Met één eigenschap.
  • Door de eerste letter van een tekenreekseigenschap.
  • Door een berekend numeriek bereik.
  • Op booleaanse predicaat of andere expressie.
  • Door een samengestelde sleutel.

Bovendien projecteren de laatste twee query's hun resultaten in een nieuw anoniem type dat alleen de voor- en familienaam van de leerling/student bevat. Zie de groepscomponent voor meer informatie.

Voorbeeld van groeperen op één eigenschap

In het volgende voorbeeld ziet u hoe u bronelementen groeperen met behulp van één eigenschap van het element als groepssleutel. De sleutel is een enum, het jaar van de student op school. De groeperingsbewerking maakt gebruik van de standaard gelijkheidsvergelijker voor het type.

var groupByYearQuery =
    from student in students
    group student by student.Year into newGroup
    orderby newGroup.Key
    select newGroup;

foreach (var yearGroup in groupByYearQuery)
{
    Console.WriteLine($"Key: {yearGroup.Key}");
    foreach (var student in yearGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

De equivalente code met behulp van methodesyntaxis wordt weergegeven in het volgende voorbeeld:

// Variable groupByLastNamesQuery is an IEnumerable<IGrouping<string,
// DataClass.Student>>.
var groupByYearQuery = students
    .GroupBy(student => student.Year)
    .OrderBy(newGroup => newGroup.Key);

foreach (var yearGroup in groupByYearQuery)
{
    Console.WriteLine($"Key: {yearGroup.Key}");
    foreach (var student in yearGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

Voorbeeld van groeperen op waarde

In het volgende voorbeeld ziet u hoe u bronelementen groeperen met behulp van iets anders dan een eigenschap van het object voor de groepssleutel. In dit voorbeeld is de sleutel de eerste letter van de familienaam van de leerling/student.

var groupByFirstLetterQuery =
    from student in students
    let firstLetter = student.LastName[0]
    group student by firstLetter;

foreach (var studentGroup in groupByFirstLetterQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key}");
    foreach (var student in studentGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

Geneste foreach is vereist voor toegang tot groepsitems.

De equivalente code met behulp van methodesyntaxis wordt weergegeven in het volgende voorbeeld:

var groupByFirstLetterQuery = students
    .GroupBy(student => student.LastName[0]);

foreach (var studentGroup in groupByFirstLetterQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key}");
    foreach (var student in studentGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

Groeperen op een bereikvoorbeeld

In het volgende voorbeeld ziet u hoe u bronelementen groeperen met behulp van een numeriek bereik als groepssleutel. De query projecteert vervolgens de resultaten in een anoniem type dat alleen de voor- en familienaam en het percentielbereik waartoe de student behoort, bevat. Een anoniem type wordt gebruikt omdat het niet nodig is om het volledige Student object te gebruiken om de resultaten weer te geven. GetPercentile is een helperfunctie waarmee een percentiel wordt berekend op basis van de gemiddelde score van de student. De methode retourneert een geheel getal tussen 0 en 10.

static int GetPercentile(Student s)
{
    double avg = s.Scores.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;

foreach (var studentGroup in groupByPercentileQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key * 10}");
    foreach (var item in studentGroup)
    {
        Console.WriteLine($"\t{item.LastName}, {item.FirstName}");
    }
}

Geneste foreach is vereist om groepen en groepitems te herhalen. De equivalente code met behulp van methodesyntaxis wordt weergegeven in het volgende voorbeeld:

static int GetPercentile(Student s)
{
    double avg = s.Scores.Average();
    return avg > 0 ? (int)avg / 10 : 0;
}

var groupByPercentileQuery = students
    .Select(student => new { student, percentile = GetPercentile(student) })
    .GroupBy(student => student.percentile)
    .Select(percentGroup => new
    {
        percentGroup.Key,
        Students = percentGroup.Select(s => new { s.student.FirstName, s.student.LastName })
    })
    .OrderBy(percentGroup => percentGroup.Key);

foreach (var studentGroup in groupByPercentileQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key * 10}");
    foreach (var item in studentGroup.Students)
    {
        Console.WriteLine($"\t{item.LastName}, {item.FirstName}");
    }
}

Voorbeeld van groeperen op vergelijking

In het volgende voorbeeld ziet u hoe u bronelementen groeperen met behulp van een Boole-vergelijkingsexpressie. In dit voorbeeld test de Boole-expressie of de gemiddelde examenscore van een student groter is dan 75. Net als in eerdere voorbeelden worden de resultaten geprojecteerd in een anoniem type omdat het volledige bronelement niet nodig is. De eigenschappen in het anonieme type worden eigenschappen van het Key lid.

var groupByHighAverageQuery =
    from student in students
    group new
    {
        student.FirstName,
        student.LastName
    } by student.Scores.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}");
    }
}

De equivalente query met behulp van de methodesyntaxis wordt weergegeven in de volgende code:

var groupByHighAverageQuery = students
    .GroupBy(student => student.Scores.Average() > 75)
    .Select(group => new
    {
        group.Key,
        Students = group.AsEnumerable().Select(s => new { s.FirstName, s.LastName })
    });

foreach (var studentGroup in groupByHighAverageQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key}");
    foreach (var student in studentGroup.Students)
    {
        Console.WriteLine($"\t{student.FirstName} {student.LastName}");
    }
}

Groeperen op anoniem type

In het volgende voorbeeld ziet u hoe u een anoniem type gebruikt om een sleutel in te kapselen die meerdere waarden bevat. In dit voorbeeld is de eerste sleutelwaarde de eerste letter van de familienaam van de leerling/student. De tweede sleutelwaarde is een Booleaanse waarde die aangeeft of de student meer dan 85 heeft gescoord op het eerste examen. U kunt de groepen op elke eigenschap in de sleutel bestellen.

var groupByCompoundKey =
    from student in students
    group student by new
    {
        FirstLetterOfLastName = student.LastName[0],
        IsScoreOver85 = student.Scores[0] > 85
    } into studentGroup
    orderby studentGroup.Key.FirstLetterOfLastName
    select studentGroup;

foreach (var scoreGroup in groupByCompoundKey)
{
    var s = scoreGroup.Key.IsScoreOver85 ? "more than 85" : "less than 85";
    Console.WriteLine($"Name starts with {scoreGroup.Key.FirstLetterOfLastName} who scored {s}");
    foreach (var item in scoreGroup)
    {
        Console.WriteLine($"\t{item.FirstName} {item.LastName}");
    }
}

De equivalente query met behulp van de methodesyntaxis wordt weergegeven in de volgende code:

var groupByCompoundKey = students
    .GroupBy(student => new
    {
        FirstLetterOfLastName = student.LastName[0],
        IsScoreOver85 = student.Scores[0] > 85
    })
    .OrderBy(studentGroup => studentGroup.Key.FirstLetterOfLastName);

foreach (var scoreGroup in groupByCompoundKey)
{
    var s = scoreGroup.Key.IsScoreOver85 ? "more than 85" : "less than 85";
    Console.WriteLine($"Name starts with {scoreGroup.Key.FirstLetterOfLastName} who scored {s}");
    foreach (var item in scoreGroup)
    {
        Console.WriteLine($"\t{item.FirstName} {item.LastName}");
    }
}

Een geneste groep maken

In het volgende voorbeeld ziet u hoe u geneste groepen maakt in een LINQ-queryexpressie. Elke groep die wordt gemaakt op basis van het niveau studentenjaar of cijfer wordt vervolgens verder onderverdeeld in groepen op basis van de namen van de personen.

var nestedGroupsQuery =
    from student in students
    group student by student.Year into newGroup1
    from newGroup2 in
    from student in newGroup1
    group student by student.LastName
    group newGroup2 by newGroup1.Key;

foreach (var outerGroup in nestedGroupsQuery)
{
    Console.WriteLine($"DataClass.Student Level = {outerGroup.Key}");
    foreach (var innerGroup in outerGroup)
    {
        Console.WriteLine($"\tNames that begin with: {innerGroup.Key}");
        foreach (var innerGroupElement in innerGroup)
        {
            Console.WriteLine($"\t\t{innerGroupElement.LastName} {innerGroupElement.FirstName}");
        }
    }
}

Er zijn drie geneste foreach lussen vereist om de binnenste elementen van een geneste groep te herhalen.
(Beweeg de muisaanwijzer over de iteratievariabelen, outerGroupen innerGroupinnerGroupElement om het werkelijke type te zien.)

De equivalente query met behulp van de methodesyntaxis wordt weergegeven in de volgende code:

var nestedGroupsQuery =
    students
    .GroupBy(student => student.Year)
    .Select(newGroup1 => new
    {
        newGroup1.Key,
        NestedGroup = newGroup1
            .GroupBy(student => student.LastName)
    });

foreach (var outerGroup in nestedGroupsQuery)
{
    Console.WriteLine($"DataClass.Student Level = {outerGroup.Key}");
    foreach (var innerGroup in outerGroup.NestedGroup)
    {
        Console.WriteLine($"\tNames that begin with: {innerGroup.Key}");
        foreach (var innerGroupElement in innerGroup)
        {
            Console.WriteLine($"\t\t{innerGroupElement.LastName} {innerGroupElement.FirstName}");
        }
    }
}

Een subquery uitvoeren op een groeperingsbewerking

In dit artikel worden twee verschillende manieren beschreven om een query te maken waarmee de brongegevens in groepen worden geordeld en vervolgens een subquery voor elke groep afzonderlijk wordt uitgevoerd. De basistechniek in elk voorbeeld is om de bronelementen te groeperen met behulp van een vervolg met de naam newGroupen vervolgens een nieuwe subquery te genereren op newGroupbasis van . Deze subquery wordt uitgevoerd voor elke nieuwe groep die door de buitenste query is gemaakt. In dit specifieke voorbeeld is de uiteindelijke uitvoer geen groep, maar een platte reeks anonieme typen.

Zie de groepscomponent voor meer informatie over het groeperen. Zie voor meer informatie over vervolgen. In het volgende voorbeeld wordt een in-memory gegevensstructuur gebruikt als de gegevensbron, maar dezelfde principes zijn van toepassing op elk type LINQ-gegevensbron.

var queryGroupMax =
    from student in students
    group student by student.Year into studentGroup
    select new
    {
        Level = studentGroup.Key,
        HighestScore = (
            from student2 in studentGroup
            select student2.Scores.Average()
        ).Max()
    };

var count = queryGroupMax.Count();
Console.WriteLine($"Number of groups = {count}");

foreach (var item in queryGroupMax)
{
    Console.WriteLine($"  {item.Level} Highest Score={item.HighestScore}");
}

De query in het voorgaande codefragment kan ook worden geschreven met behulp van de syntaxis van de methode. Het volgende codefragment heeft een semantisch equivalente query die is geschreven met behulp van de syntaxis van de methode.

var queryGroupMax =
    students
        .GroupBy(student => student.Year)
        .Select(studentGroup => new
        {
            Level = studentGroup.Key,
            HighestScore = studentGroup.Max(student2 => student2.Scores.Average())
        });

var count = queryGroupMax.Count();
Console.WriteLine($"Number of groups = {count}");

foreach (var item in queryGroupMax)
{
    Console.WriteLine($"  {item.Level} Highest Score={item.HighestScore}");
}

Zie ook