Porta tutti i bambini a una sola lista – C # ricorsiva

C # | .NET 4.5 | Entity Framework 5

Ho una class in Entity Framework che assomiglia a questo:

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

L’ID è l’identificativo della posizione, ParentID lo collega a un genitore e Children contiene tutti i percorsi figli della posizione genitore. Sto cercando un modo semplice, probabilmente in modo ricorsivo, per far sì che tutta “Posizione” e i loro figli si trovino in un’unica Lista contenente i Location.ID. Ho problemi a concettualizzare questo in modo ricorsivo. Qualsiasi aiuto è apprezzato.

Questo è quello che ho finora, è un’estensione della class di quadro, ma credo che potrebbe essere fatto meglio / più semplice:

 public List GetAllDescendants() { List returnList = new List(); List result = new List(); result.AddRange(GetAllDescendants(this, returnList)); return result; } public List GetAllDescendants(Location oID, ICollection list) { list.Add(oID); foreach (Location o in oID.Children) { if (o.ID != oID.ID) GetAllDescendants(o, list); } return list.ToList(); } 

AGGIORNATO

Ho finito per scrivere la ricorsione in SQL, lanciando quella in un SP, e poi trasferendola in Entity. Sembrava più pulito e più semplice per me che usare Linq e, a giudicare dai commenti, Linq ed Entity non sembrano la strada migliore da percorrere. Grazie per tutto l’aiuto!

Puoi fare SelectMany

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

Puoi usare dove condizioni per alcuni risultati selettivi come

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

Se hai solo bisogno di Id’s of Children puoi farlo

 List idResult = myLocationList.SelectMany(x => x.Children) .SelectMany(x => x.ID).ToList(); 

Prova questo metodo di estensione:

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

E puoi usarlo in questo modo:

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

Il framework Entity al momento non supporta la ricorsione, e per questo motivo è ansible

  • Affidati alle raccolte di dati di caricamento pigro come hai fatto (attenti al problema N + 1)
  • Interrogare una profondità arbitraria di oggetti (Questa sarà una brutta query, sebbene tu possa generarla usando System.Linq.Expressions)

L’unica opzione reale sarebbe evitare l’uso di LINQ per esprimere la query e ricorrere invece allo standard SQL.

Il framework Entity supporta questo scenario abbastanza bene, sia che tu stia usando il codice prima o no.

Per il code-first, considera qualcosa sulla falsariga di

 var results = this.db.Database.SqlQuery(rawSqlQuery) 

Per il model-first, considera l’utilizzo di una query di definizione che ritengo sia una buona opzione in quanto consente un’ulteriore composizione o stored procedure.

Per recuperare ricorsivamente i dati, è necessario comprendere le CTE ricorsive assumendo che si stia utilizzando SQL Server e che sia la versione 2005+

MODIFICARE:

Ecco il codice per una query ricorsiva a una profondità arbitraria. Lo metto insieme solo per divertimento, dubito che sarebbe molto efficiente!

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

L’elenco appiattito si trova nella query variabile

Questo farà il trucco:

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

Usalo in questo modo:

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

Vorrei contribuire con la mia soluzione, che è stata modificata dai seguenti riferimenti:

 public static IEnumerable Flatten(this IEnumerable source, Func recursion) where R : IEnumerable { var flattened = source.ToList(); var children = source.Select(recursion); if (children != null) { foreach (var child in children) { flattened.AddRange(child.Flatten(recursion)); } } return flattened; } 

Esempio:

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

Produzione:

  • Dominic
  • Brittany

Classe:

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

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

Nota: Imansible trovare l’altro riferimento, ma qualcun altro su SO ha pubblicato una risposta da cui ho copiato del codice.

Non avevo Children nel mio modello, quindi la risposta di Nikhil Agrawal non funziona per me, quindi ecco la mia soluzione.

Con il seguente modello:

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

Puoi avere figli di un object usando:

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

Per es.

 List foos = new List(); 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) 

Assuming Locations è un DbSet nel tuo contesto DB, questo risolverà il tuo problema “Sto cercando un modo semplice … per portare tutti” Location “e i loro figli in una sola Lista contenente i Location.ID”. Sembra che mi manchi qualcosa, quindi per favore chiarisci se è così.

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

Crea una lista per aggiungere tutti i child usando ricorsivamente statico pubblico Elenco list = new List ();

funzione ricorsiva

  static void GetChild(int id) // Pass parent Id { using (var ctx = new CodingPracticeDataSourceEntities()) { if (ctx.Trees.Any(x => x.ParentId == id)) { var childList = ctx.Trees.Where(x => x.ParentId == id).ToList(); list.AddRange(childList); foreach (var item in childList) { GetChild(item.Id); } } } } 

Modello di esempio

  public partial class Tree { public int Id { get; set; } public string Name { get; set; } public Nullable ParentId { get; set; } }