Как выбрать рекурсивные вложенные объекты, используя LINQ to Entity - PullRequest
9 голосов
/ 24 марта 2011

У меня есть объект с именем Category, а объект содержит IEnumerable с именем ChildCategories.Категория может иметь эти дочерние категории, которые могут иметь свои собственные дочерние категории и т. Д.

Допустим, я выбрал родительскую категорию верхнего уровня, я хочу получить все дочерние категории и их дочерние категории и т. Д.,что у меня есть все иерархические дети категории.Я хочу, чтобы это льстило и возвращалось с начальной категорией.Я пытался создать что-то вроде

    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 ]

15 голосов
/ 24 марта 2011

Вы не сможете сделать что-то подобное только с одним LINQ;LINQ не имеет поддержки для обхода неизвестного уровня узлов из коробки.

Кроме того, у вас нет реального способа уплощения структуры, количества требуемых свойствнеизвестно (поскольку оно связано с глубиной дерева, что также неизвестно).

Я бы порекомендовал использовать итераторы в 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 на возвращаемое значение непозволительно.

7 голосов
/ 24 марта 2011

В своем блоге Перейдите иерархическую структуру с LINQ-to-Hierarchical , Арджан Эйнбу описывает метод уплощения иерархий для упрощения запросов:

Могу ли я создать универсальный метод расширения, который сгладит любую иерархию? [...]

Для этого нам нужно проанализировать, какие части метода нужно поменять местами. Это было бы свойством Узлов TreeNode. Можем ли мы получить к этому доступ другим способом? Да, я думаю, что делегат может помочь нам, поэтому давайте попробуем:

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 также описывает это в своем ответе , наряду с проблемами, присущими попытке пройти по иерархии напрямую с помощью LINQ.

0 голосов
/ 03 декабря 2014

Там, где некоторые проблемы с кодом 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;
            }
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...