2011-03-24 64 views
8

我有一個名爲Category的實體,實體包含一個名爲ChildCategories的IEnumerable。一個類別可以有這些子類別,它可以有它自己的子類別等等。如何選擇使用LINQ to Entity的遞歸嵌套實體

說我已經選擇了頂級父類別,我想要獲取所有子類別及其子類別等,以便我擁有該類別的所有分層子級。我想要這個奉承,並返回最初的類別。我試着創造一些像

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後得到flatterned但沒有帶完全得到它。

回答

6

在他的博客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層次結構中固有的問題一起。

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

-1

那裏有一些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; 
      } 
     } 
    } 
+0

這應該是@ caserOne的回答評論 – Dude0001 2015-04-24 14:57:48