Практическое руководство. Группировка результатов запросов (Руководство по программированию на C#)
Группирование — одна из самых эффективных функций LINQ. В следующих примерах демонстрируются различные способы группирования данных:
По отдельному свойству.
По первой букве строкового свойства.
По расчетному числовому диапазону.
По логическому предикату или другому выражению.
По составному ключу.
Кроме того, два последних запроса передают свои результаты в новый анонимный тип, содержащий только имя и фамилию студента. Дополнительные сведения см. в разделе Предложение group (Справочник по C#).
Пример
Во всех примерах в данном разделе используются следующие вспомогательные классы и источники данных.
public class StudentClass
{
#region data
protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear };
protected class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public GradeLevel Year;
public List<int> ExamScores;
}
protected static List<Student> students = new List<Student>
{
new Student {FirstName = "Terry", LastName = "Adams", ID = 120,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 99, 82, 81, 79}},
new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 99, 86, 90, 94}},
new Student {FirstName = "Hanying", LastName = "Feng", ID = 117,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 93, 92, 80, 87}},
new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 97, 89, 85, 82}},
new Student {FirstName = "Debra", LastName = "Garcia", ID = 115,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 35, 72, 91, 70}},
new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 92, 90, 83, 78}},
new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 88, 94, 65, 91}},
new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 75, 84, 91, 39}},
new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111,
Year = GradeLevel.SecondYear,
ExamScores = new List<int>{ 97, 92, 81, 60}},
new Student {FirstName = "Lance", LastName = "Tucker", ID = 119,
Year = GradeLevel.ThirdYear,
ExamScores = new List<int>{ 68, 79, 88, 92}},
new Student {FirstName = "Michael", LastName = "Tucker", ID = 122,
Year = GradeLevel.FirstYear,
ExamScores = new List<int>{ 94, 92, 91, 91}},
new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121,
Year = GradeLevel.FourthYear,
ExamScores = new List<int>{ 96, 85, 91, 60}}
};
#endregion
//Helper method, used in GroupByRange.
protected static int GetPercentile(Student s)
{
double avg = s.ExamScores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}
public void QueryHighScores(int exam, int score)
{
var highScores = from student in students
where student.ExamScores[exam] > score
select new {Name = student.FirstName, Score = student.ExamScores[exam]};
foreach (var item in highScores)
{
Console.WriteLine("{0,-15}{1}", item.Name, item.Score);
}
}
}
public class Program
{
public static void Main()
{
StudentClass sc = new StudentClass();
sc.QueryHighScores(1, 90);
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
В следующем примере показывается группировка элементов источника с помощью использования отдельного свойства элемента в качестве ключа группы. В данном случае в качестве ключа используется string, фамилия учащегося. Для ключа можно также использовать подстроку. При операции группирования используется компаратор проверки на равенство, используемый по умолчанию для данного типа.
Вставьте приведенный ниже метод в класс StudentClass. Измените оператор вызова в методе Main на sc.GroupBySingleProperty().
public void GroupBySingleProperty()
{
Console.WriteLine("Group by a single property in an object:");
// Variable queryLastNames is an IEnumerable<IGrouping<string,
// DataClass.Student>>.
var queryLastNames =
from student in students
group student by student.LastName into newGroup
orderby newGroup.Key
select newGroup;
foreach (var nameGroup in queryLastNames)
{
Console.WriteLine("Key: {0}", nameGroup.Key);
foreach (var student in nameGroup)
{
Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
}
}
}
/* Output:
Group by a single property in an object:
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
*/
В следующем примере показывается группировка элементов источника, когда в качестве ключа группы используется не свойство объекта. В данном случае в качестве ключа используется первая буква фамилии учащегося.
Вставьте приведенный ниже метод в класс StudentClass. Измените оператор вызова в методе Main на sc.GroupBySubstring().
public void GroupBySubstring()
{
Console.WriteLine("\r\nGroup by something other than a property of the object:");
var queryFirstLetters =
from student in students
group student by student.LastName[0];
foreach (var studentGroup in queryFirstLetters)
{
Console.WriteLine("Key: {0}", studentGroup.Key);
// Nested foreach is required to access group items.
foreach (var student in studentGroup)
{
Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
}
}
}
/* Output:
Group by something other than a property of the object:
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.
//Helper method, used in GroupByRange.
protected static int GetPercentile(Student s)
{
double avg = s.ExamScores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}
Вставьте приведенный ниже метод в класс StudentClass. Измените оператор вызова в методе Main на sc.GroupByRange().
public void GroupByRange()
{
Console.WriteLine("\r\nGroup by numeric range and project into a new anonymous type:");
var queryNumericRange =
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 queryNumericRange)
{
Console.WriteLine("Key: {0}", (studentGroup.Key * 10));
foreach (var item in studentGroup)
{
Console.WriteLine("\t{0}, {1}", item.LastName, item.FirstName);
}
}
}
/* Output:
Group by numeric range and project into a new anonymous type:
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, а при выполнении запроса доступ к ним можно получить по имени.
Вставьте приведенный ниже метод в класс StudentClass. Измените оператор вызова в методе Main на sc.GroupByBoolean().
public void GroupByBoolean()
{
Console.WriteLine("\r\nGroup by a Boolean into two groups with string keys");
Console.WriteLine("\"True\" and \"False\" and project into a new anonymous type:");
var queryGroupByAverages = from student in students
group new { student.FirstName, student.LastName }
by student.ExamScores.Average() > 75 into studentGroup
select studentGroup;
foreach (var studentGroup in queryGroupByAverages)
{
Console.WriteLine("Key: {0}", studentGroup.Key);
foreach (var student in studentGroup)
Console.WriteLine("\t{0} {1}", student.FirstName, student.LastName);
}
}
/* Output:
Group by a Boolean into two groups with string keys
"True" and "False" and project into a new anonymous type:
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 баллов на первом экзамене. Группы можно сортировать по любому из свойств в данном ключе.
Вставьте приведенный ниже метод в класс StudentClass. Измените оператор вызова в методе Main на sc.GroupByCompositeKey().
public void GroupByCompositeKey()
{
var queryHighScoreGroups =
from student in students
group student by new { FirstLetter = student.LastName[0],
Score = student.ExamScores[0] > 85 } into studentGroup
orderby studentGroup.Key.FirstLetter
select studentGroup;
Console.WriteLine("\r\nGroup and order by a compound key:");
foreach (var scoreGroup in queryHighScoreGroups)
{
string s = scoreGroup.Key.Score == true ? "more than" : "less than";
Console.WriteLine("Name starts with {0} who scored {1} 85", scoreGroup.Key.FirstLetter, s);
foreach (var item in scoreGroup)
{
Console.WriteLine("\t{0} {1}", item.FirstName, item.LastName);
}
}
}
/* Output:
Group and order by a compound key:
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
*/
Компиляция кода
Копируйте и вставьте каждый метод, подлежащий проверке, в класс StudentClass. Добавьте оператор вызова для метода в метод Main и нажмите клавишу F5.
Настраивая данные методы для собственного приложения, важно помнить, что для LINQ требуется .NET Framework версии 3.5 или 4 и что проект должен содержать ссылку на библиотеку System.Core.dll и директиву using для библиотеки System.Linq. Типа LINQ to SQL, LINQ to XML и LINQ to DataSet требуют дополнительных директив using и ссылок. Дополнительные сведения см. в разделе Практическое руководство. Создание проекта LINQ.
См. также
Задачи
Практическое руководство. Создание вложенной группы (Руководство по программированию на C#)
Ссылки
Предложение group (Справочник по C#)
Анонимные типы (Руководство по программированию в C#)
Основные понятия
Выражения запросов LINQ (Руководство по программированию на C#)