32

I am using Entity Framework (version 6) to map to a recursive hierarchy and it maps nicely.

My issue is that I want to recursively get ALL child nodes of a particular node in the hierarchy.

I get the child nodes quite easily using Linq:

var recursiveList = db.ProcessHierarchyItems
            .Where(x => x.id == id)
            .SelectMany(x => x.Children);

Does anybody know of a clean implementation, that will recursively get all children?

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Keith Doran
  • 434
  • 1
  • 7
  • 14
  • 1
    possible duplicate of [How to do recursive load with Entity framework?](http://stackoverflow.com/questions/2266473/how-to-do-recursive-load-with-entity-framework) – BartoszKP Jan 07 '14 at 15:14

5 Answers5

67

While it is possible to use a recursive method here, you can traverse this tree structure using an explicit stack instead to avoid using the stack space, which isn't always sufficient for large tree structures. Such a method is also very nice as an iterator block, and iterator blocks are much more expensive when recursive than regular methods, so this will perform better as well:

public static IEnumerable<T> Traverse<T>(this IEnumerable<T> items, 
    Func<T, IEnumerable<T>> childSelector)
{
    var stack = new Stack<T>(items);
    while(stack.Any())
    {
        var next = stack.Pop();
        yield return next;
        foreach(var child in childSelector(next))
            stack.Push(child);
    }
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • 2
    Thanks - Can you provide an example of how to invoke this method. I have tried: var descendents = db.ProcessHierarchyItems.Traverse(x => x.Id == id); – Keith Doran Jan 07 '14 at 15:49
  • @user3168594 The lambda needs to return all of the children for that node, as per the signature, so as per your question that looks like it'd be: `x => x.Children` – Servy Jan 07 '14 at 15:56
  • Nearly working - my linq is now: 'var descendents = db.ProcessHierarchyItems.Where(x=>x.id == idOfParentNode).Traverse(x=>x.Children);' - Traverse is now bringing back ALL the children but also the parent - Is this correct? – Keith Doran Jan 07 '14 at 16:13
  • @user3168594 That was what it was designed to do. You could change its definition if that is the behavior you need. You can simply yield children, rather than the current node, for example. – Servy Jan 07 '14 at 16:15
  • Can you edit the above so that it doesn't add parent to the stack? :) – Keith Doran Jan 07 '14 at 16:26
  • 7
    @user3168594 You are more than welcome to adjust the code to suit your own needs; I'm confident in your ability to reach a solution given this answer. – Servy Jan 07 '14 at 16:28
  • I do not use EF at all and use another Linq providers. So I need to ask you guys, what is resulting SQL query executed on DB with EF6 using this Traverse method? – Maxim Dec 06 '19 at 06:53
  • 2
    @Maxim This method accepts an `IEnumerable`, not an `IQueryable`, and as such only operates on in-memory collections. It cannot be translated into a DB query. – Servy Dec 07 '19 at 18:39
  • Noted that this answer will enumerate the items to create the stack. – shtse8 Mar 12 '21 at 09:12
14

Thanks Servy, I expanded on your code a bit to allow for iterating single items, as well as collections. I came across when looking for a way to find out if an exception or any inner exceptions were of a certain type, but this will have many uses.

Here is a fiddle with examples, test cases, etc. dotnetfiddle LinqTraversal

Just the helpers:

public static class LinqRecursiveHelper
{
    /// <summary>
    /// Return item and all children recursively.
    /// </summary>
    /// <typeparam name="T">Type of item.</typeparam>
    /// <param name="item">The item to be traversed.</param>
    /// <param name="childSelector">Child property selector.</param>
    /// <returns></returns>
    public static IEnumerable<T> Traverse<T>(this T item, Func<T, T> childSelector)
    {
        var stack = new Stack<T>(new T[] { item });

        while (stack.Any())
        {
            var next = stack.Pop();
            if (next != null)
            {
                yield return next;
                stack.Push(childSelector(next));
            }
        }
    }

    /// <summary>
    /// Return item and all children recursively.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="item"></param>
    /// <param name="childSelector"></param>
    /// <returns></returns>
    public static IEnumerable<T> Traverse<T>(this T item, Func<T, IEnumerable<T>> childSelector)
    {
        var stack = new Stack<T>(new T[] { item });

        while (stack.Any())
        {
            var next = stack.Pop();
            //if(next != null)
            //{
            yield return next;
            foreach (var child in childSelector(next))
            {
                stack.Push(child);
            }
            //}
        }
    }

    /// <summary>
    /// Return item and all children recursively.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="items"></param>
    /// <param name="childSelector"></param>
    /// <returns></returns>
    public static IEnumerable<T> Traverse<T>(this IEnumerable<T> items,
      Func<T, IEnumerable<T>> childSelector)
    {
        var stack = new Stack<T>(items);
        while (stack.Any())
        {
            var next = stack.Pop();
            yield return next;
            foreach (var child in childSelector(next))
                stack.Push(child);
        }
    }
}
Duane McKinney
  • 397
  • 3
  • 5
  • Not sure if I'm following the proper etiquette here. I didn't know if an edit would be more appropriate than a new answer based on another one. – Duane McKinney Sep 18 '15 at 15:32
  • Ok, I felt like I was reinventing the wheel with this and I was. In combination with this answer [Passing a single item as IEnumerable](http://stackoverflow.com/questions/1577822/passing-a-single-item-as-ienumerablet), you can just use SelectMany to return only the children, and SelectMany().Contat(root) – Duane McKinney Sep 18 '15 at 16:02
  • And SelectMany isn't recursive, so my original solution stands. – Duane McKinney Sep 18 '15 at 17:28
  • 1
    I tried you solution and tweaked it a little to support nullable childs. Maybe you should update your answer in order to help some others facing the same nullable problem, but good work! – DirtyNative Nov 22 '20 at 20:23
2

Try this. While other answers enumerate the enumerable while building the enumerable, we should consider building it without enumerating it.

public static IEnumerable<T> SelectRecursively<T>(this T source, Func<T, IEnumerable<T>> selector)
{
        return selector(source).SelectMany(x => x.SelectRecursively(selector).Prepend(x));
}
shtse8
  • 1,092
  • 12
  • 20
1

The simplest solution seems to introduce a recursive method. You can't get recursive just by LINQ itself:

IEnumerable<X> GetChildren(X x)
{
    foreach (var rChild in x.Children.SelectMany(child => GetChildren(child)))
    {
        yield return rChild;
    }
}

If you have lazy loading, then this should work:

var recursiveList = db.ProcessHierarchyItems
        .Where(x => x.id == id)
        .AsEnumerable()
        .SelectMany(x => GetChildren(x));
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • I have GetChildren defined as: IEnumerable GetChildren(ProcessHierarchyItem x) - However I get an error:"LINQ to Entities does not recognize the method 'System.Collections.Generic.IEnumerable`1[RedOwlMvc.Models.ProcessHierarchyItem] GetChildren(RedOwlMvc.Models.ProcessHierarchyItem)' method, and this method cannot be translated into a store expression." – Keith Doran Jan 07 '14 at 15:10
  • @user3168594 Oh, so in this case I've marked your question as a duplicate. Please read the answers in the link that was automatically posted under your question. – BartoszKP Jan 07 '14 at 15:15
  • @user3168594 You can also try my simple fix - see the edited answer. – BartoszKP Jan 07 '14 at 15:16
  • Thanks for the reply - your change got rid of the error, but is not returning any children – Keith Doran Jan 07 '14 at 15:27
  • @user3168594 That's why I suggested reading the linked question and answers. This is complicated, and I'm not going to copy everything from other thread... : ) – BartoszKP Jan 07 '14 at 15:28
  • This only worked for me when I added a `yield return x` after the `foreach`. – g t Apr 03 '14 at 10:30
  • 1
    @gt Yeah, it depends whether you want only leaves or intermediate nodes as well. – BartoszKP Apr 03 '14 at 12:10
  • +1 "You can't get recursive just by LINQ itself". At least from what I have seen, this is correct? Mb you can extend link through a recursive expression tree or methods like above. – TamusJRoyce Nov 03 '17 at 16:10
1

I like more the linq way to do recursive.

public static IEnumerable<TReturn> Recursive<TItem, TReturn>(this TItem item, Func<TItem, IEnumerable<TReturn>> select, Func<TItem, IEnumerable<TItem>> recurrence)
    {
        return select(item).Union(recurrence(item).Recursive(select, recurrence));
    }
ggrocco
  • 209
  • 2
  • 3
  • 3
    It looks cool, but I'm a little confused about the parameters. (item, select, and recurrence). Could you please briefly describe them? Thanks – Mustafa Mohammadi Mar 08 '18 at 04:46