Linq is a great technology to manage data directly from your .Net language.

One of its features is grouping. Many people understand grouping like it is defined in Sql. Linq is implementing grouping quite the same way. Let's discover this syntax and how to make consecutive groups easier.

Then we will show how to use WPF HierarchicalDataTemplate to expose the results in just a few lines.

Let's assume we have a collection of customer: 'customers'. You just have to use 'group by' to define groups among your data.

var q = from c in db.Customers group c by c.Country;

q then becomes an enumeration of groups (IQueryable<IGrouping<string, Customer>>).
Each item of this enumeration defines a group (IGrouping<string, Cucstomer>).

As we can see in its definition, IGrouping is just adding a few things:

- the key of the group (country in our sample).
- the items grouped by this common key. To retrieve these items, you have to browse the group which is an enumeration itself.

foreach (var g in q) { var city = g.Key; foreach (var c in g) Console.WriteLine(c.CompanyName); }

IGrouping<,> definition:
// Summary: // Represents a collection of objects that have a common key. // // Type parameters: // TKey: // The type of the key of the System.Linq.IGrouping<TKey,TElement>. // // TElement: // The type of the values in the System.Linq.IGrouping<TKey,TElement>. public interface IGrouping<TKey, TElement> : IEnumerable<TElement>, IEnumerable { // Summary: // Gets the key of the System.Linq.IGrouping<TKey,TElement>. // // Returns: // The key of the System.Linq.IGrouping<TKey,TElement>. TKey Key { get; } }

Most of the time, we are using groups to retrieve aggregations like sum or count.
To do this using Linq you just have to build a new Linq query on our first group query.
var q = from g in (from c in db.Customers group c by c.Country) select new { g.Key, Count = g.Count() };

To simplify this syntax, you can use the 'into' keyword and then make disappear the nested query. Do not forget the first syntax that makes more visible why 'c' is not reachable after the group statement. Like in Sql, once the data are grouped, you can only select properties from the group.

var q = from c in db.Customers group c by c.Country into g select new { g.Key, Count = g.Count() };

Now, let's try to create child groups inside this query. The goal is simple: I would like to group customers by Countries then by Cities inside each group. This is quite the same scenario than what we are doing when using pivot table in excel.

We can write it 'manually' nesting a second Linq query into the result of our first query:

var q = from c in db.Customers group c by c.Country into g select new { g.Key, Count = g.Count(), SubGroups = from c in g group c by c.City into g2 select g2};

The result is a tree of items grouped in a first level of countries groups and then each country group has a SubGroups property that stores the group of cities contained in each country.

Writing this will become less and less readable when the number of child groups will grow.
Moreover, it's quite hard to factorize this code as we have to insert a new query inside the last projection.
I wanted to make this scenario more simple and more generic. Here is the idea.

The first thing I have done is to create a fixed type to define a group.
This allows me to have a returnable type (anonymous types are not), so I can isolate my code in a method. Moreover, as I will use my method recursively, actually I had no choice!
Another reason was having the fixed non generic type GroupResult makes it easier for me to use WPF data binding (xaml does not support generic types).

public class GroupResult { public object Key { get; set; } public int Count { get; set; } public IEnumerable Items { get; set; } public IEnumerable<GroupResult> SubGroups { get; set; } public override string ToString() { return string.Format("{0} ({1})", Key, Count); } }

Ok, now let's write the main work. The GroupByMany method is extending IEnumerable<T> just like GroupBy does but you can add an undefined number of group selectors (params Func<TElement, TKey>[] groupSelectors).

If the number of group selectors is zero, then the method returns null. It's also what will stop the recursivity in the case of multiple selectors.

If the number of group selectors is greater than zero, then I isolate the first one and build a simple Linq GroupBy query using it. Each returned item is a GroupResult and I am calling recursively the GroupByMany method on the results of the group (g) to fill the SubGroups property. When calling this method, I am using the remaining unused group selectors, which will finish by being empty.

public static class MyEnumerableExtensions { public static IEnumerable<GroupResult> GroupByMany<TElement>( this IEnumerable<TElement> elements, params Func<TElement, object>[] groupSelectors) { if (groupSelectors.Length > 0) { var selector = groupSelectors.First(); //reduce the list recursively until zero var nextSelectors = groupSelectors.Skip(1).ToArray(); return elements.GroupBy(selector).Select( g => new GroupResult { Key = g.Key, Count = g.Count(), Items = g, SubGroups = g.GroupByMany(nextSelectors) }); } else return null; } }

Now, let's use it:
var result = customers.GroupByMany(c => c.Country, c => c.City);

The calling code is short and easy to read and the GroupByMany method is factorized enough to use it in many other cases.

Last step, let's try to display the result. WPF has a feature that I love: the possibility to associate a template to a data type. Usually, a template is stored in the resources and is indexed with a key, then the controls reference this template. Using the 'DataType' syntax without key, the template is automatically associated to any content control when the type of the content is corresponding to the DataType of the template.

The HierarchicalDataTemplate is a special template that allows you to define a collection of children (ItemsSource property) in addition to the regular DataTemplate definition.

Some hierarchical controls like the TreeView are using this template to build their structure recursively. So we have nothing more to do to display our multiple groupby results than connecting them to the treeview:

DataContext = customers.GroupByMany(c => c.Country, c => c.City);
<Window x:Class="WPFGroupingTemplate.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WPFGroupingTemplate" Title="Window1" Height="600" Width="800" Loaded="Window_Loaded"> <Window.Resources> <HierarchicalDataTemplate DataType="{x:Type local:GroupResult}" ItemsSource="{Binding SubGroups}"> <TextBlock Text="{Binding}" /> </HierarchicalDataTemplate> </Window.Resources> <DockPanel> <TreeView x:Name="groups" Width="200" DockPanel.Dock="Left" ItemsSource="{Binding}" /> <ListView ItemsSource="{Binding ElementName=groups, Path=SelectedItem.Items}"> <ListView.View> <GridView> <GridViewColumn Header="Company name" DisplayMemberBinding="{Binding CompanyName}" /> <GridViewColumn Header="Contact name" DisplayMemberBinding="{Binding ContactName}" /> <GridViewColumn Header="Country" DisplayMemberBinding="{Binding Country}" /> <GridViewColumn Header="City" DisplayMemberBinding="{Binding City}" /> </GridView> </ListView.View> </ListView> </DockPanel> </Window>

In addition to the treeview, I have added a simple ListView to display the customers belonging to the current selected group.

image 

I let you evaluate the size of the code if you had to write the same program using Windows Forms and ADO.Net...

The source code attached is for Visual Studio 2008. Even if this sample is using Linq to object grouping, I have used a local Northwind database to populate my collection. You just need to modify the connection string in the app.config file to run the sample (or use any of your data sources of course).