Procedura: raggruppare i risultati di una query (Guida per programmatori C#)

Il raggruppamento è una delle funzionalità più potenti di LINQ. Nell'esempio riportato di seguito viene illustrato come raggruppare i dati con diverse modalità:

  • In base a una singola proprietà.

  • In base alla prima lettera di una proprietà di stringa.

  • In base a un intervallo numerico calcolato.

  • In base al predicato booleano o altra espressione.

  • In base a una chiave composta.

Le ultime due query proiettano inoltre i risultati in un tipo anonimo nuovo che contiene solo il nome e il cognome dello studente. Per ulteriori informazioni, vedere Clausola group (Riferimento C#).

Esempio

In tutti gli esempi di questo argomento vengono utilizzate le classi di supporto e le origini dati seguenti.

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

Nell'esempio seguente viene illustrato come raggruppare elementi di origine utilizzando una sola proprietà dell'elemento come chiave di gruppo. In questo caso, la chiave è un oggetto string, il cognome dello studente. È anche possibile utilizzare una sottostringa per la chiave. L'operazione di raggruppamento utilizza l'operatore di confronto di uguaglianza predefinito per il tipo.

Incollare il metodo seguente nella classe StudentClass. Cambiare l'istruzione di chiamata nel metodo Main in 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
*/

Nell'esempio seguente viene illustrato come raggruppare elementi di origine utilizzando uno strumento diverso da una proprietà dell'oggetto per la chiave di gruppo. In questo esempio la chiave è la prima lettera del cognome dello studente.

Incollare il metodo seguente nella classe StudentClass. Cambiare l'istruzione di chiamata nel metodo Main in 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
*/

Nell'esempio seguente viene illustrato come raggruppare elementi di origine utilizzando un intervallo numerico come chiave di gruppo. La query proietta quindi i risultati in un tipo anonimo che contiene solo il nome e il cognome e l'intervallo percentile al quale appartiene lo studente. Viene utilizzato un tipo anonimo perché non è necessario utilizzare l'oggetto Student completo per visualizzare i risultati. GetPercentile è una funzione di supporto che consente di calcolare un percentile basato sul voto medio dello studente. Il metodo restituisce un Integer compreso tra 0 e 10.

//Helper method, used in GroupByRange.
protected static int GetPercentile(Student s)
{
    double avg = s.ExamScores.Average();
    return avg > 0 ? (int)avg / 10 : 0;
}

Incollare il metodo seguente nella classe StudentClass. Cambiare l'istruzione di chiamata nel metodo Main in 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
*/

Nell'esempio seguente viene illustrato come raggruppare elementi di origine utilizzando un'espressione di confronto booleana. In questo esempio l'espressione booleana consente di verificare se il voto medio degli esami di uno studente è maggiore di 75. Come negli esempi precedenti, i risultati vengono proiettati in un tipo anonimo perché l'elemento di origine completo non è necessario. Si noti che le proprietà nel tipo anonimo diventano proprietà sul membro Key e sono accessibili tramite il nome quando la query viene eseguita.

Incollare il metodo seguente nella classe StudentClass. Cambiare l'istruzione di chiamata nel metodo Main in 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
*/

Nell'esempio seguente viene illustrato come utilizzare un tipo anonimo per incapsulare una chiave che contiene più valori. In questo esempio il primo valore della chiave è la prima lettera del cognome dello studente. Il secondo valore della chiave è un valore booleano che specifica se lo studente ha ottenuto un voto superiore a 85 nel primo esame. È possibile ordinare i gruppi in base a qualsiasi proprietà nella chiave.

Incollare il metodo seguente nella classe StudentClass. Cambiare l'istruzione di chiamata nel metodo Main in 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
*/

Compilazione del codice

Copiare e incollare ogni metodo che si desidera testare nella classe StudentClass. Aggiungere un'istruzione di chiamata per il metodo al metodo Main e premere F5.

Quando si adattano questi metodi alla propria applicazione, ricordare che LINQ richiede la versione 3.5 o 4 di .NET Framework e che il progetto deve contenere un riferimento a System.Core.dll e una direttiva using per System.Linq. I tipi LINQ to SQL, LINQ to XML e LINQ to DataSet richiedono direttive using e riferimenti aggiuntivi. Per ulteriori informazioni, vedere Procedura: creare un progetto LINQ.

Vedere anche

Attività

Procedura: eseguire una sottoquery su un'operazione di raggruppamento (Guida per programmatori C#)

Procedura: creare un gruppo annidato (Guida per programmatori C#)

Riferimenti

Clausola group (Riferimento C#)

Tipi anonimi (Guida per programmatori C#)

GroupBy

IGrouping<TKey, TElement>

Concetti

Espressioni di query LINQ (Guida per programmatori C#)

Raggruppamento dei dati