Gruppera data (C#)
Gruppering syftar på åtgärden att placera data i grupper så att elementen i varje grupp delar ett gemensamt attribut. Följande bild visar resultatet av att gruppera en sekvens med tecken. Nyckeln för varje grupp är tecknet.
Viktigt!
Dessa exempel använder en System.Collections.Generic.IEnumerable<T> datakälla. Datakällor baserade på System.Linq.IQueryProvider användning av System.Linq.IQueryable<T> datakällor och uttrycksträd. Uttrycksträd har begränsningar för den tillåtna C#-syntaxen. Dessutom kan varje IQueryProvider
datakälla, till exempel EF Core , införa fler begränsningar. Kontrollera dokumentationen för din datakälla.
Standardmetoderna för frågeoperatorer som grupperar dataelement visas i följande tabell.
Metodnamn | beskrivning | Syntax för C#-frågeuttryck | Mer information |
---|---|---|---|
GroupBy | Grupperar element som delar ett gemensamt attribut. Ett IGrouping<TKey,TElement> objekt representerar varje grupp. | group … by -eller- group … by … into … |
Enumerable.GroupBy Queryable.GroupBy |
ToLookup | Infogar element i en Lookup<TKey,TElement> (en en-till-många-ordlista) baserat på en nyckelväljare. | Ej tillämpbart. | Enumerable.ToLookup |
I följande kodexempel används group by
-satsen för att gruppera heltal i en lista beroende på om de är jämna eller udda.
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);
}
}
Motsvarande fråga med metodsyntax visas i följande kod:
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);
}
}
Kommentar
I följande exempel i den här artikeln används vanliga datakällor för det här området.
Var Student
och en har en betygsnivå, en primär avdelning och en serie poäng. En Teacher
har också en City
egenskap som identifierar det campus där läraren har klasser. A Department
har ett namn och en referens till en Teacher
som fungerar som avdelningschef.
Du hittar exempeldatauppsättningen i källdatabasen.
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; }
}
Gruppfrågeresultat
Gruppering är en av de mest kraftfulla funktionerna i LINQ. Följande exempel visar hur du grupperar data på olika sätt:
- Med en enda egenskap.
- Med den första bokstaven i en strängegenskap.
- Efter ett beräknat numeriskt intervall.
- Efter booleskt predikat eller annat uttryck.
- Med en sammansatt nyckel.
Dessutom projicerar de två sista frågorna sina resultat till en ny anonym typ som endast innehåller elevens för- och efternamn. Mer information finns i gruppsatsen.
Gruppera efter exempel på enskild egenskap
I följande exempel visas hur du grupperar källelement med hjälp av en enda egenskap för elementet som gruppnyckel. Nyckeln är ett enum
, elevens år i skolan. Grupperingsåtgärden använder standardjämlikhetsjämföraren för typen.
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}");
}
}
Motsvarande kod med hjälp av metodsyntax visas i följande exempel:
// 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}");
}
}
Exempel på gruppering efter värde
I följande exempel visas hur du grupperar källelement med hjälp av något annat än en egenskap för objektet för gruppnyckeln. I det här exemplet är nyckeln den första bokstaven i elevens familjenamn.
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}");
}
}
Kapslad foreach krävs för åtkomst till gruppobjekt.
Motsvarande kod med hjälp av metodsyntax visas i följande exempel:
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}");
}
}
Gruppera efter ett intervallexempel
I följande exempel visas hur du grupperar källelement med hjälp av ett numeriskt intervall som en gruppnyckel. Frågan projicerar sedan resultatet till en anonym typ som endast innehåller det första namnet och familjenamnet och percentilintervallet som eleven tillhör. En anonym typ används eftersom det inte är nödvändigt att använda det fullständiga Student
objektet för att visa resultatet. GetPercentile
är en hjälpfunktion som beräknar en percentil baserat på elevens genomsnittliga poäng. Metoden returnerar ett heltal mellan 0 och 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}");
}
}
Kapslad foreach krävs för att iterera över grupper och gruppobjekt. Motsvarande kod med hjälp av metodsyntax visas i följande exempel:
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}");
}
}
Gruppera efter jämförelseexempel
I följande exempel visas hur du grupperar källelement med hjälp av ett booleskt jämförelseuttryck. I det här exemplet testar det booleska uttrycket om en elevs genomsnittliga examenspoäng är större än 75. Precis som i tidigare exempel projiceras resultaten till en anonym typ eftersom det fullständiga källelementet inte behövs. Egenskaperna i den anonyma typen blir egenskaper för Key
medlemmen.
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}");
}
}
Motsvarande fråga med metodsyntax visas i följande kod:
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}");
}
}
Gruppera efter anonym typ
I följande exempel visas hur du använder en anonym typ för att kapsla in en nyckel som innehåller flera värden. I det här exemplet är det första nyckelvärdet den första bokstaven i elevens familjenamn. Det andra nyckelvärdet är ett booleskt värde som anger om eleven fick över 85 poäng på det första provet. Du kan beställa grupperna efter valfri egenskap i nyckeln.
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}");
}
}
Motsvarande fråga med metodsyntax visas i följande kod:
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}");
}
}
Skapa en kapslad grupp
I följande exempel visas hur du skapar kapslade grupper i ett LINQ-frågeuttryck. Varje grupp som skapas enligt elevår eller betygsnivå delas sedan in ytterligare i grupper baserat på individernas namn.
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}");
}
}
}
Tre kapslade foreach
loopar krävs för att iterera över de inre elementen i en kapslad grupp.
(Hovra musmarkören över iterationsvariablerna , outerGroup
, innerGroup
och innerGroupElement
för att se deras faktiska typ.)
Motsvarande fråga med metodsyntax visas i följande kod:
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}");
}
}
}
Utföra en underfråga i en grupperingsåtgärd
Den här artikeln visar två olika sätt att skapa en fråga som beställer källdata i grupper och sedan utför en underfråga över varje grupp individuellt. Den grundläggande tekniken i varje exempel är att gruppera källelementen med hjälp av en fortsättning med namnet newGroup
, och sedan generera en ny underfråga mot newGroup
. Den här underfrågan körs mot varje ny grupp som skapas av den yttre frågan. I det här exemplet är de slutliga utdata inte en grupp, utan en platt sekvens av anonyma typer.
Mer information om hur du grupperar finns i gruppsatsen. Mer information om fortsättningar finns i. I följande exempel används en minnesintern datastruktur som datakälla, men samma principer gäller för alla typer av LINQ-datakällor.
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}");
}
Frågan i föregående kodfragment kan också skrivas med hjälp av metodsyntax. Följande kodfragment har en semantiskt likvärdig fråga skriven med hjälp av metodsyntax.
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}");
}