web-dev-qa-db-fra.com

J'ai tous les enfants à une seule liste - C récursive

C # | .NET 4.5 | Entity Framework 5

J'ai une classe dans Entity Framework qui ressemble à ceci:

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

ID est l'identifiant de l'emplacement, ParentID le lie à un parent et Enfants contient tous les emplacements enfants de l'emplacement parent. Je cherche un moyen facile, probablement récursif, d’obtenir tous les "Lieu" et leurs enfants en une seule liste contenant le Lieu.ID. J'ai du mal à conceptualiser cela de manière récursive. Toute aide est appréciée.

C’est ce que j’ai eu jusqu’à présent, c’est une extension de la classe d’entités, mais je pense que cela pourrait être amélioré/simplifié:

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

MIS &AGRAVE; JOUR

J'ai fini par écrire la récursion en SQL, en lançant cela dans un SP, puis en tirant cela dans Entity. Cela me semblait plus propre et plus facile que d’utiliser Linq, et à en juger par les commentaires, Linq et Entity ne semblent pas être la meilleure voie à suivre. Merci pour toute l'aide!

10
Will

Vous pouvez faire SelectMany

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

Vous pouvez utiliser où condition pour certains résultats sélectifs comme

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

Si vous n'avez besoin que d'un ID d'enfants, vous pouvez le faire

List<long> idResult = myLocationList.SelectMany(x => x.Children)
                                    .SelectMany(x => x.ID).ToList();
11
Nikhil Agrawal

Essayez cette méthode d'extension:

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

Et vous pouvez l'utiliser comme ceci:

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

Ça fera l'affaire:

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

Utilisez-le comme ceci:

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();
5

J'aimerais apporter ma propre solution, qui a été modifiée à partir des références ci-dessous:

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

Exemple:

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

Sortie: 

  • Dominic
  • Bretagne

Classe:

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

Réf. https://stackoverflow.com/a/21054096/1477388

Remarque: Vous ne trouvez pas l'autre référence, mais quelqu'un d'autre sur SO a publié une réponse à partir de laquelle j'ai copié du code.

4
user1477388

Entity Framework ne prend actuellement pas en charge la récursivité. Pour cette raison, vous pouvez soit

  • Fiez-vous au chargement paresseux de collections enfants comme vous l'avez fait (attention au problème N + 1)
  • Interroger une profondeur arbitraire d'objets (il s'agira d'une requête laide, bien que vous puissiez la générer à l'aide de System.Linq.Expressions)

La seule véritable option serait d'éviter d'utiliser LINQ pour exprimer la requête et de recourir à la norme SQL.

Entity Framework supporte assez bien ce scénario, que vous utilisiez du code en premier ou non.

Pour le code d'abord, considérons quelque chose comme:

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

Pour le modèle d’abord, envisagez d’utiliser une requête define qui, à mon avis, est une bonne option car elle autorise une composition ultérieure, ou des procédures stockées.

Pour récupérer des données de manière récursive, vous devez comprendre les CTE récursifs en supposant que vous utilisez SQL Server et qu'il s'agit de la version 2005+.

MODIFIER:

Voici le code pour une requête récursive à une profondeur arbitraire. Je mets ça ensemble pour le plaisir, je doute que ce soit très efficace!

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

La liste aplatie est dans la requête de variable

4
Martin Booth

Je n'avais aucun accessoire Children dans mon modèle, donc la réponse de Nikhil Agrawal ne fonctionne pas pour moi, voici donc ma solution.

Avec modèle suivant:

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

Vous pouvez obtenir des enfants d'un article en utilisant:

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

Par ex.

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)
1
Mehdi Dehghani

Créer une liste pour ajouter tous les enfants en utilisant récursivement Public statique List list = new List (); 

fonction récursive

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

                        }

                    }
                }

Exemple de modèle

 public partial class Tree
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Nullable<int> ParentId { get; set; }
    }
1
Muhammed Afsal

En supposant que Locations soit un DbSet<Location> dans votre contexte de base de données, cela résoudra votre problème "Je cherche un moyen simple ... de regrouper tous les 'Location' et leurs enfants dans une seule liste contenant les identifiants de Location.ID". On dirait que je manque quelque chose, alors s'il vous plaît préciser si oui.

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