Entity Framework - Inclure plusieurs niveaux de propriétés

376

La méthode Include () fonctionne assez bien pour les listes d'objets. Mais que se passe-t-il si j'ai besoin d'aller deux niveaux en profondeur? Par exemple, la méthode ci-dessous retournera ApplicationServers avec les propriétés incluses affichées ici. Cependant, ApplicationsWithOverrideGroup est un autre conteneur qui contient d'autres objets complexes. Puis-je également faire un Include () sur cette propriété? Ou comment puis-je charger complètement cette propriété?

En l'état actuel, cette méthode:

public IEnumerable<ApplicationServer> GetAll()
{
    return this.Database.ApplicationServers
        .Include(x => x.ApplicationsWithOverrideGroup)                
        .Include(x => x.ApplicationWithGroupToForceInstallList)
        .Include(x => x.CustomVariableGroups)                
        .ToList();
}

Remplira uniquement la propriété Enabled (ci-dessous) et non les propriétés Application ou CustomVariableGroup (ci-dessous). Comment puis-je y arriver?

public class ApplicationWithOverrideVariableGroup : EntityBase
{
    public bool Enabled { get; set; }
    public Application Application { get; set; }
    public CustomVariableGroup CustomVariableGroup { get; set; }
}
Bob Horn
la source
Salut, Pourquoi je reçois une exception Expression must be a member expressionquand j'essaie ceci: Pour inclure une collection et une collection un niveau plus bas: query.Include(e => e.Level1Collection.Select(l1 => l1.Level2Collection)).
Joe.wang
1
@BobHorn, j'ai le même problème .. Dans mon cas, l'imbrication va profondément dans plusieurs couches, j'ai réussi à faire une inclusion comme vous l'avez souligné. Dans le SQL qui a été généré, je pouvais voir toutes les colonnes renvoyer avec un nom d'alias différent comme c1, c2 quelque chose comme ça. Ma question est de savoir comment je peux former une collection DTO imbriquée de toutes mes inclusions :( .. Peut-être que vous pouvez prendre l'exemple ci-dessus lui-même, en ce sens que nous renvoyons toutes les colonnes sans DTO personnalisé (qui est lui-même une collection de DTO) )
TechQuery

Réponses:

705

Pour EF 6

using System.Data.Entity;

query.Include(x => x.Collection.Select(y => y.Property))

Assurez-vous d'ajouter using System.Data.Entity;pour obtenir la version de Includecette prise dans un lambda.


Pour EF Core

Utilisez la nouvelle méthode ThenInclude

query.Include(x => x.Collection)
     .ThenInclude(x => x.Property);
Diego Torres
la source
1
Je ne peux pas faire Include () sur ApplicationsWithOverrideGroup. Il n'apparaît pas dans Intellisense.
Bob Horn
Je ne peux pas utiliser votre modification car ApplicationsWithOverrideGroup est une liste. L'application est une propriété sur chaque élément de la liste, pas sur la liste elle-même.
Bob Horn
1
Ahhhh, mais ce lien que vous avez fourni semble fournir la réponse. Permettez-moi d'essayer ceci: pour inclure une collection puis une collection d'un niveau plus bas: query.Include (e => e.Level1Collection.Select (l1 => l1.Level2Collection)).
Bob Horn
60
N'oubliez pas d'inclure System.Data.Entity dans les utilisations. Sinon, Intellisense ne vous fournira que la version Inclure (chemin de chaîne) de la méthode.
JO Raqueño du
5
@Adeem vous devez appeler Includepour chaque propriété:Db.States.Include(state => state.Cities.Select(city => city.Customers).Include(state => state.Cities.Select(city => city.Vendors)
Diego Torres
72

Si je vous comprends bien, vous demandez l'ajout de propriétés imbriquées. Si c'est le cas :

.Include(x => x.ApplicationsWithOverrideGroup.NestedProp)

ou

.Include("ApplicationsWithOverrideGroup.NestedProp")  

ou

.Include($"{nameof(ApplicationsWithOverrideGroup)}.{nameof(NestedProp)}")  
Judo
la source
6
Merci, je peux essayer ça. J'espérais pouvoir garder les choses fortement tapées et éviter les littéraux de chaîne. Mais si c'est comme ça que ça doit être fait ...
Bob Horn
1
Tu étais proche. Je n'ai peut-être pas été clair que ApplicationsWithOverrideGroup était une liste. Merci pour ton aide!
Bob Horn
@Judo, j'ai le même problème .. Dans mon cas, l'imbrication va en profondeur sur plusieurs couches, j'ai réussi à faire une inclusion comme vous l'avez souligné. Dans le SQL qui a été généré, je pouvais voir toutes les colonnes renvoyer avec un nom d'alias différent comme c1, c2 quelque chose comme ça. Ma question est de savoir comment je peux former une collection DTO imbriquée de toutes mes inclusions :( .. Peut-être que vous pouvez prendre l'exemple ci-dessus lui-même, en ce sens que nous renvoyons toutes les colonnes sans DTO personnalisé (qui est lui-même une collection de DTO) )
TechQuery
2
N'oubliez pas d'inclure System.Data.Entity dans les utilisations. Sinon, Intellisense ne vous donnera que la Include(string path)version de la méthode.
AlexMelw
53

EF Core: Utilisation de "ThenInclude" pour charger plusieurs niveaux: Par exemple:

var blogs = context.Blogs
    .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .ThenInclude(author => author.Photo)
    .ToList();
thangcao
la source
53
On dirait que c'est EF Core uniquement
Chris Marisic
27
FYI: VS2017 l'intellisense ne fonctionnait pas pour .ThenInclude. Tapez simplement comment vous pensez que cela devrait être et la surbrillance d'erreur devrait disparaître.
JohnWrensby
4
Je tiens à souligner le commentaire de @JohnWrensby, l'Intellisense peut parfois prendre beaucoup de temps pour gérer ces ThenInclude, cela peut être assez déroutant pour les nouveaux utilisateurs. J'ai également eu des cas où l'expression simple Inclure lambda n'était pas gérée correctement, jusqu'à ce que vous la tapiez et la compiliez, en ignorant les "erreurs" affichées dans VS.
Pac0
@ Pac0 tu m'as sauvé la journée. du mal à voir les articles pour enfants et ne pouvait pas.
Bendram
28

J'ai fait un petit assistant pour Entity Framework 6 (style .Net Core), pour inclure les sous-entités de manière agréable.

C'est maintenant sur NuGet: Install-Package ThenInclude.EF6

using System.Data.Entity;

var thenInclude = context.One.Include(x => x.Twoes)
    .ThenInclude(x=> x.Threes)
    .ThenInclude(x=> x.Fours)
    .ThenInclude(x=> x.Fives)
    .ThenInclude(x => x.Sixes)
    .Include(x=> x.Other)
    .ToList();

Le package est disponible sur GitHub .

Lenny32
la source
salut, j'ai une exception à l'exécution, ne peut pas convertir IncludableQueryable <observablecollection> en IncludableQueryable <genericcollection>
user2475096
j'utilise db en premier et j'ai modifié le fichier tt pour obtenir ObservableCollections pour toutes mes entités, toute aide est la bienvenue.
user2475096
2
@ lenny32 quelque chose à savoir avec cette extension?
Aaron Hudon
Notez que cela n'est pas nécessaire si la propriété vers laquelle vous naviguez est en tête-à-tête avec le DbSet à partir duquel vous avez navigué, et vous pouvez chaîner DbSet<One>().Include(x => x.Two.Three.Four.Five.Six)avec le seul inconvénient étant que vous calculez un produit cartésien et augmentez potentiellement la bande passante.
John Zabroski
23

Plus d' exemples EFCore sur MSDN montrent que vous pouvez faire des choses assez complexes avec Includeet ThenInclude.

Ceci est un bon exemple de la complexité que vous pouvez obtenir (ce n'est qu'une seule déclaration!):

viewModel.Instructors = await _context.Instructors

      .Include(i => i.OfficeAssignment)

      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)

      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)

      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

Voyez comment vous pouvez enchaîner Includemême après ThenIncludeet cela vous ramène au niveau de l'entité de niveau supérieur (instructeurs).

Vous pouvez même répéter plusieurs fois la même collection de «premier niveau» (CourseAssignments) suivie de ThenIncludescommandes distinctes pour accéder à différentes entités enfants.

Notez que votre requête réelle doit être balisée à la fin de la chaîne Includeou ThenIncludes. Ce qui suit ne fonctionne PAS:

var query = _context.Instructors.AsQueryable();
query.Include(i => i.OfficeAssignment);

var first10Instructors = query.Take(10).ToArray();

Je vous recommande fortement de configurer la journalisation et de vous assurer que vos requêtes ne sont pas hors de contrôle si vous incluez plus d'une ou deux choses. Il est important de voir comment cela fonctionne réellement - et vous remarquerez que chaque «inclusion» distincte est généralement une nouvelle requête pour éviter que des jointures massives ne renvoient des données redondantes.

AsNoTracking peut accélérer considérablement les choses si vous n'avez pas l'intention de modifier réellement les entités et de les réenregistrer.

Simon_Weaver
la source
Existe-t-il un moyen d'obtenir à la fois l'inscription et les départements sans votre répétition. Comprend les cours et les cours? (Jusqu'à présent, il semble que l'API puisse aller plus loin avec .ThenInclude, ou revenir au niveau supérieur avec .Include, mais il n'y a rien pour rester au même niveau?)
William Jockusch
Si vous voulez un chargement paresseux restez à l'écoute pour EF Core 2.1 blogs.msdn.microsoft.com/dotnet/2018/02/02/… mais si vous voulez juste charger plus au même niveau, je pense que c'est par conception. Je ne suis pas sûr de ce que vous pensez - cela ne nécessite pas beaucoup plus pour ce faire et cela réduit considérablement ce qui revient de la base de données. Une entité peut simplement avoir une ou deux choses de «même niveau», mais elle peut également en avoir 50 pour un grand projet, étant explicite, votre application est beaucoup plus rapide.
Simon_Weaver
C'était une bonne explication du concept de la "remise à zéro" du niveau au niveau initial. M'a aidé à comprendre la hiérarchie du système Incl. À votre santé!
AFM-Horizon
22

J'ai également dû utiliser plusieurs inclusions et au 3ème niveau, j'avais besoin de plusieurs propriétés

(from e in context.JobCategorySet
                      where e.Id == id &&
                            e.AgencyId == agencyId
                      select e)
                      .Include(x => x.JobCategorySkillDetails)
                      .Include(x => x.Shifts.Select(r => r.Rate).Select(rt => rt.DurationType))
                      .Include(x => x.Shifts.Select(r => r.Rate).Select(rt => rt.RuleType))
                      .Include(x => x.Shifts.Select(r => r.Rate).Select(rt => rt.RateType))
                      .FirstOrDefaultAsync();

Cela peut aider quelqu'un :)

dnxit
la source
1
cela peut-il être fait sans répéter.Include(x => x.Shifts.Select(r => r.Rate).Select(rt => rt......
Multinerd
ça dépend, jusqu'où tu veux aller
dnxit
7

Permettez-moi de dire clairement que vous pouvez utiliser la surcharge de chaîne pour inclure des niveaux imbriqués indépendamment des multiplicités des relations correspondantes, si cela ne vous dérange pas d'utiliser des littéraux de chaîne:

query.Include("Collection.Property")
mrmashal
la source
1
Cette méthode m'a été utile pour comprendre comment cela peut être codé en VB, car je ne peux pas trouver n'importe où après des heures de recherche sur Google.
Coder
Cela fonctionne très bien pour moi, j'utilise beaucoup cela !!! Cela fonctionne même combiné avec les déclarations .SelectMany:query.SelectMany(x=>x.foos).Include("bar").Include("bar.docs")...
Ephie