Flatten a Hierarchical Collection of Objects with LINQ






4.97/5 (16 votes)
Adding an extension method to LINQ to flatten any hierarchical collection
Introduction
Recently, I had a particular requirement in a project where I had to flatten the hierarchy of an object type with children of the same type.
For example, I have an object collection of type MyObject
who have themselves a property of children MyObject
of type IEnumerable<MyObject>
. This property can be null
, empty or contain iterations.

If we have in our database a hierarchy like this:

By performing the following LINQ query...
myObjects
.Where(myObject => myObject.Id == 1)
.ToList();
...my result will be a list with the item “My Object A
”. What to do if I want to obtain the item “My Object A
” and all children under him, including children of children?

That's why I worked on an extension method to LINQ that allowed me to flatten an entire object structure of the same type on the same level.
Using the Code
Here is the extension method:
using System.Collections.Generic;
namespace System.Linq
{
public static class LinqExtensions
{
/// <summary>
/// This method extends the LINQ methods to flatten a collection of
/// items that have a property of children of the same type.
/// </summary>
/// <typeparam name = "T">Item type.</typeparam>
/// <param name = "source">Source collection.</param>
/// <param name = "childPropertySelector">
/// Child property selector delegate of each item.
/// IEnumerable'T' childPropertySelector(T itemBeingFlattened)
/// </param>
/// <returns>Returns a one level list of elements of type T.</returns>
public static IEnumerable<T> Flatten<T>(
this IEnumerable<T> source,
Func<T, IEnumerable<T>> childPropertySelector)
{
return source
.Flatten((itemBeingFlattened, objectsBeingFlattened) =>
childPropertySelector(itemBeingFlattened));
}
/// <summary>
/// This method extends the LINQ methods to flatten a collection of
/// items that have a property of children of the same type.
/// </summary>
/// <typeparam name = "T">Item type.</typeparam>
/// <param name = "source">Source collection.</param>
/// <param name = "childPropertySelector">
/// Child property selector delegate of each item.
/// IEnumerable'T' childPropertySelector
/// (T itemBeingFlattened, IEnumerable'T' objectsBeingFlattened)
/// </param>
/// <returns>Returns a one level list of elements of type T.</returns>
public static IEnumerable<T> Flatten<T>(
this IEnumerable<T> source,
Func<T, IEnumerable<T>, IEnumerable<T>> childPropertySelector)
{
return source
.Concat(source
.Where(item => childPropertySelector(item, source) != null)
.SelectMany(itemBeingFlattened =>
childPropertySelector(itemBeingFlattened, source)
.Flatten(childPropertySelector)));
}
}
}
And how to use it:
myObjects
.Where(myObject => myObject.Id == 1)
.Flatten(myObject => myObject.Children)
.ToList();
Or this way if you have a cyclical tree (or other need):
myObjects
.Where(myObject => myObject.Id == 1)
.Flatten((myObject, objectsBeingFlattened) =>
myObject.Children.Except(objectsBeingFlattened))
.ToList();
Points of Interest
This method can easily be integrated to a corporative framework. It’s very useful in cases where you have to transform a query linked to a TreeView
into a simple list.
History
Thanks to Andrew Rissing, you're right.
Remove the Except
from the method, give the responsibility to the child selector to manage if you suspect a cyclical tree.
Remove the Distinct
and let the caller do.