2013-10-08 147 views
8

C#| .NET 4.5 |實體框架5獲取所有孩子到一個列表 - 遞歸C#

我在實體框架類,看起來像這樣:

public class Location 
{ 
    public long ID {get;set;} 
    public long ParentID {get;set;} 
    public List<Location> Children {get;set;} 
} 

ID是位置的標識,PARENTID它鏈接到父,和兒童包含了所有的孩子位置父母的位置。我正在尋找一些簡單的方法,可能會遞歸地將所有「位置」和他們的孩子都放到一個包含Location.ID的列表中。我無法遞歸地概念化這個問題。任何幫助表示讚賞。

這是我到目前爲止,它的擴展實體類,但我相信這是可以做到更好/更簡單:

public List<Location> GetAllDescendants() 
{ 
    List<Location> returnList = new List<Location>(); 
    List<Location> result = new List<Location>(); 
    result.AddRange(GetAllDescendants(this, returnList)); 
    return result; 
} 

public List<Location> GetAllDescendants(Location oID, ICollection<Location> list) 
{ 
    list.Add(oID); 
    foreach (Location o in oID.Children) 
    { 
      if (o.ID != oID.ID) 
        GetAllDescendants(o, list); 
    } 
    return list.ToList(); 
} 

修訂

我最後寫的在SQL中遞歸,將其放入SP中,然後將其拖入實體中。看起來更乾淨,對我來說比使用Linq容易一些,並且Linq和Entity評論似乎並不是最佳路線。感謝所有的幫助!

+0

實體框架不包含任何與遞歸查詢有關的事情。 – Aron

+0

是的,我希望擴展此功能,請參閱我的編輯。 – Will

+0

我以爲你想要一個實體框架解決方案,而不是由實體框架支持的Linq To Object解決方案延遲加載......我研究了實體框架6源代碼並希望實際添加功能......但是Microsoft將relavent類設置爲「internal」。 BAS $%^ DS – Aron

回答

10

你可以做SelectMany

List<Location> result = myLocationList.SelectMany(x => x.Children).ToList(); 

您可以使用WHERE條件對於一些選擇性的結果一樣

List<Location> result = myLocationList.Where(y => y.ParentID == someValue) 
             .SelectMany(x => x.Children).ToList(); 

如果你只需要標識子女的,你可以做

List<long> idResult = myLocationList.SelectMany(x => x.Children) 
            .SelectMany(x => x.ID).ToList(); 
+1

這會遍歷多個層次。說一個地點是否有孩子,而這些孩子有孩子? – Will

+0

+1這比Syzmon的答案要好,也許是沒有任何數據庫結構的情況下EF開箱即可得到的最好結果。但是,您仍然會進行O(級別)數據庫調用。 – Aron

+0

或許把它寫成遞歸SQL並將其放入存儲過程會更好嗎?我真的只是在尋找這個ID,而不是真正關心整個Entity對象。 – Will

0

假設Locations是您的DB連接中的DbSet<Location>分機,這將解決你的問題「我正在尋找一些簡單的方法...將所有'位置'和他們的孩子都包含在一個包含Location.ID的列表中。好像我錯過了一些東西,所以請澄清,如果是的話。

dbContext.Locations.ToList() 
// IDs only would be dbContext.Locations.Select(l => l.ID).ToList() 
+0

抓取整個數據庫,將工作... sorta。 – Aron

+0

基於他引用的問題,它的工作原理是...... – Moho

4

實體框架目前不支持遞歸,因爲這個原因,你可以

  • 依靠延遲加載子集合爲你做(提防N + 1個問題)
  • 查詢任意深度的對象(這將是一個醜陋的查詢,雖然你可以使用System.Linq.Expressions生成它)

唯一真正的選擇是避免使用LINQ來表示查詢,而是使用標準的SQL。

無論您是否先使用代碼,實體框架都能很好地支持此場景。

對於代碼優先考慮沿線的

var results = this.db.Database.SqlQuery<ResultType>(rawSqlQuery) 

東西模型首先,可以考慮使用defining query我認爲這是一個很好的選擇,因爲它允許進一步的成分,或存儲過程。

要遞歸找回數據,您需要了解遞歸的CTE假設你使用SQL Server,而且它是2005+版本

編輯:

下面是一個遞歸查詢的代碼到任意深度。我把它放在一起只是爲了好玩,我懷疑它會非常有效!

var maxDepth = 5; 

var query = context.Locations.Where(o => o.ID == 1); 
var nextLevelQuery = query; 

for (var i = 0; i < maxDepth; i++) 
{ 
    nextLevelQuery = nextLevelQuery.SelectMany(o => o.Children); 
    query = query.Concat(nextLevelQuery); 
} 

扁平列表是變量查詢

+0

這就是我最終做的。謝謝您的幫助。 – Will

11

試試這個擴展方法:

public static IEnumerable<T> Flatten<T, R>(this IEnumerable<T> source, Func<T, R> recursion) where R : IEnumerable<T> 
{ 
    return source.SelectMany(x => (recursion(x) != null && recursion(x).Any()) ? recursion(x).Flatten(recursion) : null) 
       .Where(x => x != null); 
} 

而且你可以使用它像這樣:

locationList.Flatten(x => x.Children).Select(x => x.ID); 
+0

編寫代碼的方式不需要'R'通用參數。 – GreatAndPowerfulOz

3

我想貢獻我自己的解決方案,這是從以下參考修改:

public static IEnumerable<T> Flatten<T, R>(this IEnumerable<T> source, Func<T, R> recursion) where R : IEnumerable<T> 
{ 
    var flattened = source.ToList(); 

    var children = source.Select(recursion); 

    if (children != null) 
    { 
     foreach (var child in children) 
     { 
      flattened.AddRange(child.Flatten(recursion)); 
     } 
    } 

    return flattened; 
} 

實施例:

var n = new List<FamilyMember>() 
{ 
    new FamilyMember { Name = "Dominic", Children = new List<FamilyMember>() 
     { 
      new FamilyMember { Name = "Brittany", Children = new List<FamilyMember>() } 
     } 
    } 
}.Flatten(x => x.Children).Select(x => x.Name); 

輸出:

  • 布列塔尼

類別:

public class FamilyMember { 
    public string Name {get; set;} 
    public List<FamilyMember> Children { get; set;} 
} 

Ref。 https://stackoverflow.com/a/21054096/1477388

注意:找不到其他參考,但SO上的其他人發佈了我從中複製了一些代碼的答案。

+0

編寫代碼的方式,您不需要'R'通用參數。 – GreatAndPowerfulOz

3

這將這樣的伎倆:

class Extensions 
{ 
    public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector) 
    { 
     var result = source.SelectMany(selector); 
     if (!result.Any()) 
     { 
      return result; 
     } 
     return result.Concat(result.SelectManyRecursive(selector)); 
    } 
} 

使用方法如下:

List<Location> locations = new List<Location>(); 
// 
// your code here to get locations 
// 
List<string> IDs = locations.SelectManyRecursive(l => l.Children).Select(l => l.ID).ToList(); 
+1

如果你打算倒戈,至少要禮貌地說出原因。 – GreatAndPowerfulOz

+0

爲我工作..謝謝 –

1

我沒有Children道具在我的模型,所以尼基爾阿格拉瓦爾的回答不工作對我來說,這是我的解決方案。

用以下模型:

public class Foo 
{ 
    public int Id { get; set; } 
    public int? ParentId { get; set; } 
    // other props 
} 

可以使用獲得一個項目的孩子們:

List<Foo> GetChildren(List<Foo> foos, int id) 
{ 
    return foos 
     .Where(x => x.ParentId == id) 
     .Union(foos.Where(x => x.ParentId == id) 
      .SelectMany(y => GetChildren(foos, y.Id)) 
     ).ToList(); 
} 

對於離。

List<Foo> foos = new List<Foo>(); 

foos.Add(new Foo { Id = 1 }); 
foos.Add(new Foo { Id = 2, ParentId = 1 }); 
foos.Add(new Foo { Id = 3, ParentId = 2 }); 
foos.Add(new Foo { Id = 4 }); 

GetChild(foos, 1).Dump(); // will give you 2 and 3 (ids)