【问题标题】:Recursive Hierarchy - Recursive Query using Linq递归层次结构 - 使用 Linq 的递归查询
【发布时间】:2014-01-25 06:57:53
【问题描述】:

我正在使用实体框架(版本 6)映射到递归层次结构,它映射得很好。

我的问题是我想递归地获取层次结构中特定节点的 ALL 子节点。

我很容易使用 Linq 获得子节点:

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

有没有人知道一个干净的实现,它将递归地获取所有孩子?

【问题讨论】:

标签: c# linq entity-framework recursion


【解决方案1】:

虽然可以在此处使用递归方法,但您可以使用显式堆栈来遍历此树结构,以避免使用堆栈空间,这对于大型树结构并不总是足够的。这样的方法作为迭代器块也非常好,并且迭代器块在递归时比常规方法便宜得多,因此性能也会更好:

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

【讨论】:

  • 谢谢 - 您能否提供一个如何调用此方法的示例。我试过: var descendents = db.ProcessHierarchyItems.Traverse(x => x.Id == id);
  • @user3168594 lambda 需要根据签名返回该节点的所有子节点,因此根据您的问题看起来像这样:x =&gt; x.Children
  • 几乎可以工作 - 我的 linq 现在是:'var descendents = db.ProcessHierarchyItems.Where(x=>x.id == idOfParentNode).Traverse(x=>x.Children);' - Traverse 现在正在带回所有孩子以及父母 - 这是正确的吗?
  • @user3168594 非常欢迎您根据自己的需要调整代码;鉴于此答案,我相信您有能力找到解决方案。
  • @Maxim 此方法接受IEnumerable,而不是IQueryable,因此仅对内存中的集合进行操作。它不能被翻译成数据库查询。
【解决方案2】:

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

【讨论】:

  • 不确定我是否在这里遵守了适当的礼仪。我不知道编辑是否比基于另一个答案的新答案更合适。
  • 好吧,我觉得我正在用这个重新发明轮子,我就是。结合这个答案Passing a single item as IEnumerable<T>,您可以只使用 SelectMany 只返回孩子,然后 SelectMany().Contat(root)
  • 而且 SelectMany 不是递归的,所以我原来的解决方案是有效的。
  • 我尝试了您的解决方案并对其进行了一些调整以支持可为空的孩子。也许您应该更新您的答案,以帮助其他面临同样可空问题的其他人,但干得好!
【解决方案3】:

最简单的解决方案似乎引入了递归方法。您不能仅通过 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));

【讨论】:

  • 我将 GetChildren 定义为:IEnumerable GetChildren(ProcessHierarchyItem x) - 但是我收到错误消息:“LINQ to Entities 无法识别方法 'System.Collections.Generic.IEnumerable`1[ RedOwlMvc.Models.ProcessHierarchyItem] GetChildren(RedOwlMvc.Models.ProcessHierarchyItem)' 方法,该方法不能翻译成商店表达式。”
  • @user3168594 哦,所以在这种情况下,我已将您的问题标记为重复。请阅读您的问题下自动发布的链接中的答案。
  • @user3168594 您也可以尝试我的简单修复 - 请参阅编辑后的答案。
  • 感谢您的回复 - 您的更改消除了错误,但没有返回任何孩子
  • @gt 是的,这取决于您是否只想要叶子节点或中间节点。
【解决方案4】:

我更喜欢 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));
    }

【讨论】:

  • 它看起来很酷,但我对参数有点困惑。 (项目、选择和重复)。您能否简要介绍一下它们?谢谢
【解决方案5】:

试试这个。虽然其他答案在构建可枚举时枚举了可枚举,但我们应该考虑在不枚举的情况下构建它。

public static IEnumerable<T> SelectRecursively<T>(this T source, Func<T, IEnumerable<T>> selector)
{
        return selector(source).SelectMany(x => x.SelectRecursively(selector).Prepend(x));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-23
    • 1970-01-01
    • 1970-01-01
    • 2013-11-22
    • 2017-02-04
    • 2015-12-24
    • 1970-01-01
    相关资源
    最近更新 更多