web-dev-qa-db-ja.com

再帰階層-Linqを使用した再帰クエリ

Entity Framework(バージョン6)を使用して再帰的階層にマップしていますが、うまくマップされます。

私の問題は、階層内の特定のノードの[〜#〜] all [〜#〜]子ノードを再帰的に取得することです。

Linqを使用して子ノードを簡単に取得できます。

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

誰もが再帰的にすべての子を取得するクリーンな実装を知っていますか?

23
Keith Doran

ここでは再帰的なメソッドを使用できますが、代わりに明示的なスタックを使用してこのツリー構造を走査し、スタックスペースの使用を避けることができます。これは、大きなツリー構造には必ずしも十分ではありません。このようなメソッドはイテレータブロックとしても非常に優れており、イテレータブロックは再帰的である場合、通常のメソッドよりもはるかに安価であるため、同様にパフォーマンスが向上します。

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

Servyに感謝します。コードを少し拡張して、単一のアイテムとコレクションを反復できるようにしました。例外または内部例外が特定のタイプのものであるかどうかを調べる方法を探しているときに出会いましたが、これには多くの用途があります。

ここに例、テストケースなどのフィドルがあります。 dotnetfiddle LinqTraversal

ヘルパーのみ:

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);
        }
    }
}
10
Duane McKinney

Linqの方が再帰的であることが好きです。

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
ggrocco

最も単純な解決策は、再帰的な方法を導入するようです。 LINQ自体だけでは再帰できません。

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

遅延読み込みがある場合、これは動作するはずです:

var recursiveList = db.ProcessHierarchyItems
        .Where(x => x.id == id)
        .AsEnumerable()
        .SelectMany(x => GetChildren(x));
1
BartoszKP