35

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?

1

5 Answers 5

71

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);
    }
}
Sign up to request clarification or add additional context in comments.

9 Comments

Thanks - Can you provide an example of how to invoke this method. I have tried: var descendents = db.ProcessHierarchyItems.Traverse(x => x.Id == id);
@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
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?
@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.
@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.
|
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);
        }
    }
}

4 Comments

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.
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<T>, you can just use SelectMany to return only the children, and SelectMany().Contat(root)
And SelectMany isn't recursive, so my original solution stands.
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!
4

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));
}

Comments

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));

8 Comments

I have GetChildren defined as: IEnumerable<ProcessHierarchyItem> 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."
@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.
@user3168594 You can also try my simple fix - see the edited answer.
Thanks for the reply - your change got rid of the error, but is not returning any children
@gt Yeah, it depends whether you want only leaves or intermediate nodes as well.
|
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));
    }

1 Comment

It looks cool, but I'm a little confused about the parameters. (item, select, and recurrence). Could you please briefly describe them? Thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.