Gruppieren von Daten (C#)
Als „Gruppieren“ wird das Anordnen von Daten in Gruppen bezeichnet, sodass die Elemente in jeder Gruppe über ein gemeinsames Attribut verfügen. Die folgende Abbildung zeigt die Ergebnisse der Gruppierung einer Zeichenfolge. Der Schlüssel für jede Gruppe ist das Zeichen.
Wichtig
In diesen Beispielen wird eine System.Collections.Generic.IEnumerable<T>-Datenquelle verwendet. Datenquellen, die auf System.Linq.IQueryProvider basieren, verwenden System.Linq.IQueryable<T>-Datenquellen und Ausdrucksbaumstrukturen. Ausdrucksbaumstrukturen haben Einschränkungen für die zulässige C#-Syntax. Darüber hinaus kann jede IQueryProvider
-Datenquelle, z. B. EF Core, weitere Einschränkungen erzwingen. Konsultieren Sie die Dokumentation für Ihre Datenquelle.
Die Methoden des Standardabfrageoperators, die Datenelemente gruppieren, sind in der folgenden Tabelle aufgeführt.
Methodenname | Beschreibung | C#-Abfrageausdruckssyntax | Weitere Informationen |
---|---|---|---|
GroupBy | Gruppenelemente, die über ein gemeinsames Attribut verfügen. Jede Gruppe wird durch ein IGrouping<TKey,TElement>-Objekt dargestellt. | group … by Oder group … by … into … |
Enumerable.GroupBy Queryable.GroupBy |
ToLookup | Fügt Elemente basierend auf einer Schlüsselauswahlfunktion in eine Lookup<TKey,TElement>-Klasse (one-to-many-Wörterbuch) ein. | Nicht zutreffend. | Enumerable.ToLookup |
Im folgenden Codebeispiel wird die group by
-Klausel angewandt, um die Gruppe ganzer Zahlen in Listen mit geraden und ungeraden Zahlen zu aufzuteilen.
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);
}
}
Die entsprechende Abfrage mit Methodensyntax ist im folgenden Code dargestellt:
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);
}
}
Hinweis
In den folgenden Beispielen in diesem Artikel werden die allgemeinen Datenquellen für diesen Bereich verwendet.
Allen Student
sind eine Klassenstufe, ein primärer Fachbereich und mehrere Bewertungen zugeordnet. Teacher
verfügen auch über eine City
-Eigenschaft, die den Campus identifiziert, auf dem die Lehrkraft unterrichtet. Eine Department
hat einen Namen und einen Verweis auf eine Teacher
, die den Fachbereich leitet.
Sie finden das Beispieldatensatz im Quell-Repository.
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; }
}
Gruppieren von Abfrageergebnissen
Das Gruppieren ist eine der leistungsstärksten Funktionen von LINQ. Die folgenden Beispiele zeigen Ihnen, wie Sie Daten auf verschiedene Arten und Weisen gruppieren:
- Nach einer einzelnen Eigenschaft
- Nach dem ersten Buchstaben einer Zeichenfolgeneigenschaft
- Nach einem berechneten numerischen Bereich
- Nach einem booleschen Prädikat oder einem anderer Ausdruck
- Nach einem Verbundschlüssel
Darüber hinaus projizieren die letzten beiden Abfragen die Ergebnisse in einen neuen anonymen Typ, der nur die Vor- und Nachnamen der Lernenden enthält. Weitere Informationen finden Sie unter group-Klausel.
Beispiel für die Gruppierung nach einer einzelnen Eigenschaft
Das folgende Beispiel veranschaulicht die Gruppierung von Quellelementen mithilfe einer einzelnen Eigenschaft des Elements als Gruppenschlüssel. Der Schlüssel ist eine enum
für das Schuljahr der Lernenden. Bei der Gruppierungsoperation wird der als Standard für diesen Typ festgelegte Gleichheitsvergleich verwendet.
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}");
}
}
Der entsprechende Code mit Methodensyntax ist im folgenden Beispiel dargestellt:
// 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}");
}
}
Beispiel für die Gruppierung nach einem Wert
Das folgende Beispiel veranschaulicht die Gruppierung von Quellelemente ohne eine Objekteigenschaft als Gruppenschlüssel zu verwenden. In diesem Beispiel ist der Schlüssel der erste Buchstabe des Nachnamens der Lernenden.
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}");
}
}
Beachten Sie, dass eine geschachtelte Foreach-Schleife erforderlich ist, um auf Gruppenelemente zuzugreifen.
Der entsprechende Code mit Methodensyntax ist im folgenden Beispiel dargestellt:
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}");
}
}
Beispiel für die Gruppierung nach einem Bereich
Das folgende Beispiel veranschaulicht die Gruppierung von Quellelementen mithilfe eines numerischen Bereichs als Gruppenschlüssel. Die Abfrage projiziert dann die Ergebnisse in einen anonymen Typ, der nur den Vor- und Nachnamen und den Prozentbereich enthält, dem die Lernenden angehören. Es wird ein anonymer Typ verwendet, da es nicht notwendig ist, das gesamte Student
-Objekt zu nutzen, um die Ergebnisse anzuzeigen. GetPercentile
ist eine Hilfsfunktion, die einen Prozentwert berechnet, der auf dem Durchschnittsergebnis des Studenten basiert. Die Methode gibt eine ganze Zahl zwischen 0 und 10 zurück.
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}");
}
}
Beachten Sie, dass eine geschachtelte Foreach-Schleife zum Iterieren über Gruppen und Gruppenelemente erforderlich ist. Der entsprechende Code mit Methodensyntax ist im folgenden Beispiel dargestellt:
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}");
}
}
Beispiel für die Gruppierung nach einem Vergleich
Das folgende Beispiel veranschaulicht die Gruppierung von Vergleichselementen mithilfe eines booleschen Vergleichsausdrucks. In diesem Beispiel testet der boolesche Ausdruck, ob die durchschnittliche Bewertung der Tests eines Studenten größer als 75 ist. Wie in den vorherigen Beispielen werden die Ergebnisse in einen anonymen Typ projiziert, da nicht das gesamte Quellelement benötigt wird. Die Eigenschaften im anonymen Typ werden zu Eigenschaften im Key
-Member.
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}");
}
}
Die entsprechende Abfrage mit Methodensyntax ist im folgenden Code dargestellt:
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}");
}
}
Gruppieren nach einem anonymen Typ
Das folgende Beispiel veranschaulicht die Verwendung eines anonymen Typs für die Kapselung eines Schlüssels mit mehreren Werten. In diesem Beispiel ist der erste Schlüsselwert der erste Buchstabe des Nachnamens der Lernenden. Der zweite Schlüsselwert ist ein boolescher Wert, der angibt, ob der Student in der ersten Prüfung ein Ergebnis über 85 erzielt hat. Sie können die Gruppen anhand jeder Eigenschaft im Schlüssel sortieren.
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}");
}
}
Die entsprechende Abfrage mit Methodensyntax ist im folgenden Code dargestellt:
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}");
}
}
Erstellen einer geschachtelten Gruppe
Im folgenden Beispiel wird veranschaulicht, wie geschachtelte Gruppen in einem LINQ-Abfrageausdruck erstellt werden. Jede Gruppe, die nach Studienjahr oder Klassenstufe erstellt wird, wird basierend auf den Namen der einzelnen Personen weiter in Gruppen unterteilt.
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}");
}
}
}
Es sind drei geschachtelte foreach
-Schleifen für die Iteration über die inneren Elemente einer geschachtelten Gruppe erforderlich.
(Zeigen Sie mit dem Cursor auf die Iterationsvariablen outerGroup
, innerGroup
und innerGroupElement
, um den tatsächlichen Typ anzuzeigen.)
Die entsprechende Abfrage mit Methodensyntax ist im folgenden Code dargestellt:
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}");
}
}
}
Ausführen einer Unterabfrage für eine Gruppierungsoperation
In diesem Artikel werden zwei verschiedene Möglichkeiten gezeigt, um eine Abfrage zu erstellen, die Quelldaten in Gruppen sortiert und anschließend eine Unterabfrage für jede einzelne Gruppe ausführt. Die grundlegende Technik in jedem Beispiel besteht darin, die Quellelemente mithilfe einer Fortsetzung namens newGroup
zu gruppieren und anschließend eine neue Unterabfrage für newGroup
zu erzeugen. Diese Unterabfrage wird für jede neue Gruppe ausgeführt, die von der äußeren Abfrage erstellt wird. In diesem speziellen Beispiel ist die endgültige Ausgabe keine Gruppe, sondern eine flache Sequenz von anonymen Typen.
Weitere Informationen zum Gruppieren finden Sie unter group-Klausel. Weitere Informationen zu Fortsetzungen finden Sie unter into. Im folgenden Beispiel wird eine Datenstruktur im Arbeitsspeicher als Datenquelle verwendet, jedoch gelten für jede Art von LINQ-Datenquelle die gleichen Prinzipien.
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}");
}
Die Abfrage im obigen Codeschnipsel kann auch mit Methodensyntax geschrieben werden. Der folgende Codeausschnitt weist eine semantisch äquivalente Abfrage auf, die per Methodensyntax geschrieben wurde.
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}");
}