TOC

This article is currently in the process of being translated into Dutch (~98% done).

LINQ:

Groeperen data: de GroupBy() Method

Tot nu toe hebben we voornamelijk met lijsten van gegevens gewerkt. We hebben deze gesorteerd, gelimiteerd en omgevormd tot nieuwe objecten, maar er mist nog één belangrijk onderdeel: Het groeperen van data. Wanneer je data groepeert, neem je een lijst van iets en verdeel je deze, gebaseerd op een of meerdere eigenschappen, in verschillende groepen. Stel nu dat we een gegevensbron hebben zoals de volgende:

var users = new List<User>()
{
    new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
    new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
    new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
    new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
    new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};

Een 'flat list' van gebruiker objecten, maar het zou interessant kunnen zijn om deze gebruikers te groeperen op basis van bv. hun thuisland of hun leeftijd. Met behulp van LINQ is dit erg gemakkelijk, ook al kan het gebruik van de GroupBy() methode op het eerste gezicht een beetje verwarrend zijn. Laten we eens kijken hoe het werkt:

using System;    
using System.Collections.Generic;    
using System.Linq;    

namespace LinqGroup    
{    
    class Program    
    {    
static void Main(string[] args)    
{    
    var users = new List<User>()    
    {    
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },    
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },    
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },    
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },    
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },    
    };    
    var usersGroupedByCountry = users.GroupBy(user => user.HomeCountry);    
    foreach(var group in usersGroupedByCountry)    
    {    
Console.WriteLine("Users from " + group.Key + ":");    
foreach(var user in group)    
Console.WriteLine("* " + user.Name);
    }    
}    

public class User    
{    
    public string Name { get; set; }    

    public int Age { get; set; }    

    public string HomeCountry { get; set; }    
}    
    }    
}

The resulting output will look something like this:

Users from USA:
* John Doe
* Jane Doe
* James Doe
Users from Germany:
* Joe Doe
* Jenna Doe

Het voorbeeld kan wat aan de lange kant zijn, maar je zult je snel realiseren dat het meeste werk zit in het voorbereiden van de databron. Denk eraan dat alle data net zo goed van een XML document zouden kunnen komen of van een database. Het is dus gemakkelijker het te demonstreren met een databron die je zonder meer kunt gebruiken.

Het interessante gedeelte is waar we de usersGroupedByCountry variabele creëren. We maken die door de GroupBy() method op onze databron los te laten, waarbij we precies die parameter gebruiken om de data mee te groeperen. In dit geval wil ik de gebruikers groeperen naar land, dus die property zet ik bij de GroupBy() method. Het resultaat is een object met een Key property, die de waarde heeft van de property waarmee we groepeerden (HomeCountry in dit geval), zowel als van alle objecten die tot de groep behoren. We gebruiken dat in de volgende regels en herhalen we over de groepen die we gecreëerd hebben. En voor elke groep printen we de Key (HomeCountry) en dan herhalen we en printen al de User objects van de groep.

Eigen groep keys

Zoals je ziet is het groeperen door een bestaande property een 'makkie', maar zoals je nu inmiddels weet, zijn de LINQ methods erg flexibel. op dezelfde gemakkelijke manier kun je eigen groepen creëren, gebaseerd op wat je maar wilt. Een voorbeeld daarvan zou het volgende kunnen zijn, waar we groepen creëren, die gebaseerd zijn op de eerste twee letters van de naam van de gebruiker:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqGroup
{
    class Program
    {
static void Main(string[] args)
{
    var users = new List<User>()
    {
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
    };
    var usersGroupedByFirstLetters = users.GroupBy(user => user.Name.Substring(0, 2));
    foreach(var group in usersGroupedByFirstLetters)
    {
Console.WriteLine("Users starting with " + group.Key + ":");
foreach(var user in group)
    Console.WriteLine("* " + user.Name);
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }

    public string HomeCountry { get; set; }
}
    }
}

We gebruiken de Substring() method bij de naam om de eerste twee letters te krijgen, en vervolgens creëert LINQ de groepen van gebruikers die daarop gebaseerd zij. Het resultaat ziet er zo uit:

Users starting with Jo:
* John Doe
* Joe Doe
Users starting with Ja:
* Jane Doe
* James Doe
Users starting with Je:
* Jenna Doe

Zoals je ziet kunnen we zonder meer een method binnen de GroupBy() method aanroepen. In feite kunnen we doen wat we willen, zolang als we iets retourneren dat LINQ kan gebruiken om items te groeperen. We kunnen zelfs een method creëren die een nieuw stuk informatie over een item retourneert en dat vervolgens gebruiken om een groep te creëren, zoals we gaan doen in het volgende voorbeeld:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqGroup
{
    class Program
    {
static void Main(string[] args)
{
    var users = new List<User>()
    {
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
    };
    var usersGroupedByAgeGroup = users.GroupBy(user => user.GetAgeGroup());
    foreach(var group in usersGroupedByAgeGroup)
    {
Console.WriteLine(group.Key + ":");
foreach(var user in group)
    Console.WriteLine("* " + user.Name + " [" + user.Age + " years]");
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }

    public string HomeCountry { get; set; }

    public string GetAgeGroup()
    {
if (this.Age < 13)
    return "Children";
if (this.Age < 20)
    return "Teenagers";
return "Adults";
    }
}
    }
}

Zie hoe ik een GetAgeGroup() method heb geïmplementeerd in de User class. Die retourneert een string die de leeftijdsgroep van de gebruiker definieert en we roepen hem eenvoudigweg aan in de GroupBy() method om hem te gebruiken als groep key. Het resultaat ziet er dan zo uit:

Adults:
* John Doe [42 years]
* Jane Doe [38 years]
Teenagers:
* Joe Doe [19 years]
* Jenna Doe [19 years]
Children:
* James Doe [8 years]

Ik koos ervoor om de GetAgeGroup() method te implementeren in de User class, omdat hij soms nuttig kan zijn op andere plaatsen. Maar soms ook heb je een snel stukje logica nodig om de groepen te creëren, wat dan niet ergens anders hergebruikt hoeft te worden. In die situaties staat het je vrij om de logica meteen aan de GroupBy() method mee te geven, als een lambda expressie, zoals hier:

var usersGroupedByAgeGroup = users.GroupBy(user =>
    {
if (user.Age < 13)
    return "Children";
if (user.Age < 20)
    return "Teenagers";
return "Adults";
    });

The result is of course the same!

Groeperen via een samengestelde key

Tot nu bestonden de keys van onze groepen uit een enkele waarde, bv. een property of het resultaat van een method 'call'. Het staat je echter vrij om je eigen keys te creëren die diverse waarden bevatten: dit noemen we samengestelde (composite) keys. Een voorbeeld kan zijn een groepering van onze gebruikers, gebaseerd op zowel thuisland als leeftijd, zoals dit:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqGroup2
{
    class Program
    {
static void Main(string[] args)
{
    var users = new List<User>()
    {
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
    };

    var usersGroupedByCountryAndAge = users.GroupBy(user => new { user.HomeCountry, user.Age });
    foreach(var group in usersGroupedByCountryAndAge)
    {
Console.WriteLine("Users from " + group.Key.HomeCountry + " at the age of " + group.Key.Age + ":");
foreach (var user in group)
    Console.WriteLine("* " + user.Name + " [" + user.Age + " years]");
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }

    public string HomeCountry { get; set; }

}
    }
}

Let op de syntaxis in de GroupBy() method. In plaats van één enkele property te geven, creëren we een nieuw anoniem object, dat de HomeCountry en the Age properties bevat. LINQ creëert nu groepen gebaseerd op deze twee properties en koppelt het anonieme object aan de Key property van de groep. Het staat ons vrij beide properties te gebruiken als we de bewerking herhalen bij de groepen, zoals je kunt zien. Het resultaat ziet er dan zo uit:

Users from USA at the age of 42:
* John Doe [42 years]
Users from USA at the age of 38:
* Jane Doe [38 years]
Users from Germany at the age of 19:
* Joe Doe [19 years]
* Jenna Doe [19 years]
Users from USA at the age of 8:
* James Doe [8 years]

Zoals altijd hebben we de LINQ Method syntaxis door dit hele artikel heen gebruikt, maar sta me toe je een vergelijkingsvoorbeeld te tonen van hoe het te doen zou zijn met de LINQ Query syntaxis:

// Method syntax
var usersGroupedByCountryAndAge = users.GroupBy(user => new { user.HomeCountry, user.Age });
// Query syntax
var usersGroupedByCountryAndAgeQ = from user in users group user by new { user.HomeCountry, user.Age } into userGroup select userGroup;

Samenvatting

Zoals je waarschijnlijk ziet uit de voorbeelden in dit artikel is de GroupBy() method van LINQ extreem krachtig. Je kunt er echt jouw data mee gebruiken op nieuwe manieren, en met heel weinig code. Voorheen zou dit óf erg lastig zijn óf een relationele database vereisen. Maar met LINQ kun je om het even welke databron gebruiken die je maar wilt en nog steeds dezelfde bruikbare functionaliteit krijgen.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!