2011-03-24 8 views
8

Tengo una entidad llamada Categoría y la entidad contiene un IEnumerable llamado ChildCategories. Una categoría puede tener estas categorías secundarias que pueden tener sus propias categorías secundarias, etc.Cómo selecciono entidades anidadas recursivas usando LINQ a la Entidad

Digamos que he seleccionado la categoría principal de nivel superior, quiero obtener todas las categorías secundarias y sus categorías secundarias, y así sucesivamente para tener todos los hijos jerárquicos de la categoría. Quiero que esto quede plano y vuelva con la categoría inicial. He intentado crear algo así como

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

cual sería conseguir flatterned después de usar el método SelectMany pero havn't bastante conseguido.

Respuesta

6

En su blog Traverse a hierarchical structure with LINQ-to-Hierarchical , Arjan Einbu describe un método de aplanamiento de las jerarquías para facilitar su consulta:

¿Puedo hacer un método de extensión genérica que se aplane ninguna jerarquía? [...]

Para hacer eso, tenemos que analizar qué partes del método deben intercambiarse. Esa sería la propiedad Nodos de TreeNode. ¿Podemos acceder a eso de otra manera? Sí, creo que un delegado nos puede ayudar, por lo que permite darle una oportunidad:

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, junto con los problemas inherentes al tratar de atravesar la jerarquía directamente utilizando LINQ.

15

No podrá hacer algo como esto solo con LINQ; LINQ no tiene ningún soporte para atravesar un nivel desconocido de nodos listos para usar.

Además, no tiene ninguna forma real de aplanar la estructura, se desconoce el número de propiedades necesarias (ya que está vinculada a la profundidad del árbol, que también se desconoce).

me gustaría recomendar el uso iterators in C# para aplanar el árbol, algo como esto:

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

A continuación, se puede llamar al método de extensión y colocar los resultados en un List<T>; es casi tan plano como lo que vas a obtener.

Nota, podría arrojar fácilmente un StackOverflowException si la jerarquía es lo suficientemente profunda. A tal fin, usted realmente desea utilizar este método no recursivo:

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

La instancia Stack<T> vive en el montón y no en la pila de llamadas, por lo que no se quedará sin espacio de pila de llamadas.

Además, puede cambiar el Stack<T> al Queue<T> si desea una semántica de retorno diferente (o puede recorrer los elementos secundarios de diferentes maneras) si necesita un cierto orden.

Si necesita un pedido muy específico, solo recomendaría cambiar el orden en el método si tiene una gran cantidad de artículos que se deben atravesar, lo que hace que llamar al OrderBy sea prohibitivo.

-1

Hubo algunos problemas con el código casperOnes.Esto funciona:

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

Esto debería haber sido un comentario sobre @ caserOne's answer – Dude0001

Cuestiones relacionadas