【问题标题】:How do I select recursive nested entities using LINQ to Entity如何使用 LINQ to Entity 选择递归嵌套实体
【发布时间】:2011-03-24 16:59:03
【问题描述】:

我有一个名为 Category 的实体,该实体包含一个名为 ChildCategories 的 IEnumerable。一个类别可以有这些子类别,这些子类别可以有它自己的子类别等等。

假设我已经选择了顶级父类别,我想获取所有子类别及其子类别等等,以便我拥有该类别的所有层次子类别。我想要这个扁平化并返回初始类别。我尝试过创建类似的东西

    public static IEnumerable<T> AllChildren<T>(this IEnumerable<T> items, 
        Func<T, IEnumerable<T>> children, bool includeSelf)
    {
        foreach (var item in items)
        {
            if (includeSelf)
            {
                yield return item;
            }
            if (children != null)
            {
                foreach (var a in children(item))
                {
                    yield return a;
                    children(a).AllChildren(children, false);
                }
            }
        }
    }

在使用 SelectMany 方法后会变得平淡,但还没有完全理解。

【问题讨论】:

    标签: .net linq-to-entities


    【解决方案1】:

    您无法仅使用 LINQ 来执行此类操作; LINQ 不支持开箱即用地遍历未知级别的节点。

    此外,您没有任何真正的扁平化结构的方法,所需的属性数量是未知的(因为它与树的深度相关,这也是未知的)。

    我建议使用iterators in C# 来展平树,如下所示:

    static IEnumerable<T> Flatten(this IEnumerable<T> source, 
        Func<T, IEnumerable<T>> childrenSelector)
    {
        // Do standard error checking here.
    
        // Cycle through all of the items.
        foreach (T item in source)
        {
             // Yield the item.
             yield return item;
    
             // Yield all of the children.
             foreach (T child in childrenSelector(item).
                 Flatten(childrenSelector))
             {
                 // Yield the item.
                 yield return child;
             }            
        }
    }
    

    然后,您可以调用扩展方法并将结果放在List&lt;T&gt; 中;它与您将要得到的一样平坦。

    注意,如果层次结构足够深,您可以很容易地抛出StackOverflowException。为此,您真的很想使用这种非递归方法:

    static IEnumerable<T> Flatten(this IEnumerable<T> source, 
        Func<T, IEnumerable<T>> childSelector)
    {
        // Do standard error checking here.
    
        // Create a stack for recursion.  Push all of the items
        // onto the stack.
        var stack = new Stack<T>(source);
    
        // While there are items on the stack.
        while (stack.Count > 0)
        {
            // Pop the item.
            T item = stack.Pop();
    
            // Yield the item.
            yield return item;
    
            // Push all of the children on the stack.
            foreach (T child in childSelector(item)) stack.Push(child);
        }
    }
    

    Stack&lt;T&gt; 实例位于堆上而不是调用堆栈上,因此您不会用完调用堆栈空间。

    此外,如果您需要特定的顺序,如果您想要不同的返回语义(或者您可以以不同的方式遍历子项),您可以将Stack&lt;T&gt; 更改为Queue&lt;T&gt;

    如果您需要一个非常具体的顺序,我只建议在您有大量需要遍历的项目时更改方法中的顺序,这使得在返回值上调用 OrderBy 令人望而却步。

    【讨论】:

      【解决方案2】:

      Arjan Einbu 在他的博文 Traverse a hierarchical structure with LINQ-to-Hierarchical 中描述了一种扁平化层次结构以便于查询的方法:

      我可以制作一个通用的扩展方法来扁平化任何层次结构吗? [...]

      为此,我们需要分析方法的哪些部分需要换出。那将是 TreeNode 的 Nodes 属性。我们可以通过其他方式访问它吗?是的,我认为委托可以帮助我们,所以让我们尝试一下:

      public static IEnumerable<T> FlattenHierarchy<T>(this T node, 
                                   Func<T, IEnumerable<T>> getChildEnumerator)
      {
          yield return node;
          if(getChildEnumerator(node) != null)
          {
              foreach(var child in getChildEnumerator(node))
              {
                  foreach(var childOrDescendant 
                            in child.FlattenHierarchy(getChildEnumerator))
                  {
                      yield return childOrDescendant;
                  }
              }
          }
      }
      

      casperOne describes this in his answer as well,以及尝试使用 LINQ 直接遍历层次结构所固有的问题。

      【讨论】:

        【解决方案3】:

        casperOnes 代码存在一些问题。这有效:

        public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> childrenSelector)
            {
                // Do standard error checking here.
        
                // Cycle through all of the items.
                foreach (T item in source)
                {
                    // Yield the item.
                    yield return item;
        
                    // Yield all of the children.
                    foreach (T child in childrenSelector(item).Flatten(childrenSelector))
                    {
                        // Yield the item.
                        yield return child;
                    }
                }
            }
        

        【讨论】:

        • 这应该是对@caserOne 的回答的评论
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-07-29
        • 2011-04-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多