.NET 3.5+Grouping on Multiple Properties with LINQ
LINQ allows queries to be executed that include grouping. The GroupBy query operator and the group clause both allow grouping using a single key selector. When it is necessary to group by multiple keys, the selector must include an expression.
Grouping Data
In the LINQ to Objects tutorial I described the GroupBy standard query operator and the group clause of the query expression syntax. Each of these permits grouping of collections that support the IEnumerable<T> interface. In that article I briefly explained that you can create groups that are based upon multiple fields, properties or expressions using an anonymous type in the key selector. In this article I will give some examples.
Sample Data
To demonstrate grouping on multiple properties we need a sample class. The class that we will use will include a product name, category and sub-category. The code for the class, which should be included in a console application, is as follows:
public class Product
{
public string Name { get; set; }
public string Category { get; set; }
public string Subcategory { get; set; }
public Product(string category, string subcategory, string name)
{
Category = category;
Subcategory = subcategory;
Name = name;
}
}
We also need some sample data that includes multiple categories and subcategories. To create this data, add the following code to the Main method:
var products = new List<Product>
{
new Product("Food", "Fruit", "Apple"),
new Product("Food", "Fruit", "Banana"),
new Product("Food", "Fruit", "Orange"),
new Product("Food", "Vegetables", "Carrot"),
new Product("Food", "Vegetables", "Pea"),
new Product("Drink", "Soft Drink", "Orange Juice"),
new Product("Drink", "Soft Drink", "Lemonade"),
new Product("Drink", "Alcoholic", "Bitter"),
new Product("Drink", "Alcoholic", "Lager"),
new Product("Drink", "Alcoholic", "Vodka")
};
If you wish to group by both the category and the subcategory, you can combine the two values using an expression or an anonymous type for the key selector. In the following example an anonymous type is used.
var grouped = products.GroupBy(p => new { p.Category, p.Subcategory });
To show the results you must use two loops. An outer loop will output the group keys and a nested loop will show the products in each group:
foreach (var group in grouped)
{
Console.WriteLine(group.Key);
foreach (var product in group)
{
Console.WriteLine(" {0}", product.Name);
}
}
/* OUTPUT
{ Category = Food, Subcategory = Fruit }
Apple
Banana
Orange
{ Category = Food, Subcategory = Vegetables }
Carrot
Pea
{ Category = Drink, Subcategory = Soft Drink }
Orange Juice
Lemonade
{ Category = Drink, Subcategory = Alcoholic }
Bitter
Lager
Vodka
*/
When outputting to the console in this manner, or for logging to a text file, it would be more useful to use an expression for the grouping. This can make the category names easier to read. For example:
var grouped = products.GroupBy(p => string.Format("{0}/{1}", p.Category, p.Subcategory));
/* OUTPUT
Food/Fruit
Apple
Banana
Orange
Food/Vegetables
Carrot
Pea
Drink/Soft Drink
Orange Juice
Lemonade
Drink/Alcoholic
Bitter
Lager
Vodka
*/
As you may expect, both of the above grouping queries can be represented using the query expression syntax. The equivalent queries are:
var grouped =
from p in products
group p by new { p.Category, p.Subcategory };
var grouped =
from p in products
group p by string.Format("{0}/{1}", p.Category, p.Subcategory);
31 July 2010