Cómo: Agrupar los resultados de consultas (Guía de programación de C#)
La agrupación es una de las funciones más eficaces de LINQ.En los ejemplos siguientes se muestra cómo agrupar datos de distintas formas:
Por una propiedad única.
Por la primera letra de una propiedad de cadena.
Por un intervalo numérico calculado.
Por un predicado booleano u otra expresión.
Por una clave compuesta.
Además, las dos últimas consultas proyectan sus resultados en un nuevo tipo anónimo que sólo contiene el nombre y el apellido del alumno.Para obtener más información, vea group (Cláusula, Referencia de C#).
Ejemplo
Todos los ejemplos de este tema utilizan las clases auxiliares y los orígenes de datos siguientes.
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();
}
}
En el ejemplo siguiente se muestra cómo agrupar elementos de origen utilizando una propiedad única del elemento como clave de grupo.En este caso, la clave es string, el apellido del alumno.También se puede utilizar una subcadena para la clave.La operación de agrupación utiliza el comparador de igualdad predeterminado para el tipo.
Pegue el siguiente método en la clase StudentClass.Cambie la instrucción de llamada del método Main a 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
*/
En el ejemplo siguiente se muestra cómo agrupar elementos de origen utilizando un elemento que no sea una propiedad del objeto para la clave de grupo.En este ejemplo, la clave es la primera letra del apellido del alumno.
Pegue el siguiente método en la clase StudentClass.Cambie la instrucción de llamada del método Main a 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
*/
En el ejemplo siguiente se muestra cómo agrupar elementos de origen utilizando un intervalo numérico como clave de grupo.La consulta proyecta a continuación los resultados en un tipo anónimo que sólo contiene el nombre y el apellido del alumno y el intervalo percentil al que éste pertenece.Se utiliza un tipo anónimo porque no es necesario utilizar el objeto Student completo para mostrar los resultados.GetPercentile es una función auxiliar que calcula un percentil en función de la puntuación media del alumno.El método devuelve un entero comprendido entre 0 y 10.
//Helper method, used in GroupByRange.
protected static int GetPercentile(Student s)
{
double avg = s.ExamScores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}
Pegue el siguiente método en la clase StudentClass.Cambie la instrucción de llamada del método Main a 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
*/
En el ejemplo siguiente se muestra cómo agrupar elementos de origen utilizando una expresión de comparación booleana.En este ejemplo, la expresión booleana comprueba si la puntuación media de los exámenes de un alumno es mayor que 75.Como en ejemplos anteriores, los resultados se proyectan en un tipo anónimo porque no se necesita el elemento de origen completo.Tenga en cuenta que las propiedades del tipo anónimo pasan a ser propiedades del miembro Key y se puede tener acceso a las mismas por su nombre al ejecutar la consulta.
Pegue el siguiente método en la clase StudentClass.Cambie la instrucción de llamada del método Main a 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
*/
En el ejemplo siguiente se muestra cómo utilizar un tipo anónimo para encapsular una clave que contiene varios valores.En este ejemplo, el primer valor de clave es la primera letra del apellido del alumno.El segundo valor de clave es un valor booleano que especifica si el alumno ha obtenido una puntuación superior a 85 en el primer examen.Puede ordenar los grupos por cualquier propiedad de la clave.
Pegue el siguiente método en la clase StudentClass.Cambie la instrucción de llamada del método Main a 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
*/
Compilar el código
Copie y pegue cada método que desee probar en la clase StudentClass.Agregue una instrucción de llamada para el método en el método Main y presione F5.
Cuando adapte estos métodos a su propia aplicación, recuerde que LINQ requiere la versión 3.5 o 4 de .NET Framework y que el proyecto debe contener una referencia a System.Core.dll y una directiva using para System.Linq.Los tipos LINQ to SQL, LINQ to XML y LINQ to DataSet requieren directivas using y referencias adicionales.Para obtener más información, vea Cómo: Crear un proyecto con LINQ.
Vea también
Tareas
Cómo: Realizar una subconsulta en una operación de agrupación (Guía de programación de C#)
Cómo: Crear grupos anidados (Guía de programación de C#)
Referencia
group (Cláusula, Referencia de C#)
Tipos anónimos (Guía de programación de C#)