如何使用LINQ to Entity选择递归嵌套实体

时间:2011-03-24 16:59:03

标签: .net linq-to-entities

我有一个名为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方法之后哪个会变得扁平,但是还没有得到它。

3 个答案:

答案 0 :(得分:15)

仅使用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<T>;它和你要得到的一样平坦。

注意,如果层次结构足够深,您可以很容易地抛出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<T>实例存在于堆上,而不是存在于调用堆栈中,因此您将不会用尽调用堆栈空间。

此外,如果您需要不同的返回语义(或者您可以通过不同的方式遍历子项),则可以将Stack<T>更改为Queue<T>

如果您需要一个非常具体的订单,我建议您更改方法中的顺序,如果您有大量需要遍历的项目,这会使返回值上的OrderBy调用禁止。< / p>

答案 1 :(得分:7)

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

  

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

     

为此,我们需要分析需要换出方法的哪些部分。这将是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直接遍历层次结构时固有的问题。

答案 2 :(得分:0)

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