Requête Linq Entity Framework Inclure () plusieurs entités enfants

176

Cela peut être une question vraiment élémentaire, mais quelle est la bonne façon d'inclure plusieurs entités enfants lors de l'écriture d'une requête qui s'étend sur TROIS niveaux (ou plus)?

à- dire que j'ai 4 tables: Company, Employee, Employee_CaretEmployee_Country

L'entreprise a une relation de 1: m avec l'employé.

L'employé a une relation 1: m avec Employee_Car et Employee_Country.

Si je veux écrire une requête qui renvoie les données des 4 tables, j'écris actuellement:

Company company = context.Companies
                         .Include("Employee.Employee_Car")
                         .Include("Employee.Employee_Country")
                         .FirstOrDefault(c => c.Id == companyID);

Il doit y avoir une manière plus élégante! Ceci est long et génère un SQL horrible

J'utilise EF4 avec VS 2010

Nathan Liu
la source

Réponses:

201

Utilisez des méthodes d'extension . Remplacez NameOfContext par le nom de votre contexte d'objet.

public static class Extensions{
   public static IQueryable<Company> CompleteCompanies(this NameOfContext context){
         return context.Companies
             .Include("Employee.Employee_Car")
             .Include("Employee.Employee_Country") ;
     }

     public static Company CompanyById(this NameOfContext context, int companyID){
         return context.Companies
             .Include("Employee.Employee_Car")
             .Include("Employee.Employee_Country")
             .FirstOrDefault(c => c.Id == companyID) ;
      }

}

Alors votre code devient

     Company company = 
          context.CompleteCompanies().FirstOrDefault(c => c.Id == companyID);

     //or if you want even more
     Company company = 
          context.CompanyById(companyID);
Rien
la source
Mais j'aimerais l'utiliser comme ceci: //inside public static class Extensions public static IQueryable<Company> CompleteCompanies(this DbSet<Company> table){ return table .Include("Employee.Employee_Car") .Include("Employee.Employee_Country") ; } //code will be... Company company = context.Companies.CompleteCompanies().FirstOrDefault(c => c.Id == companyID); //same for next advanced method
Hamid
Bullsye Nix. Les extensions devraient être le premier port d'escale pour ... enfin ... étendre les fonctionnalités prédéfinies.
ComeIn
12
Des années plus tard, je ne recommanderais pas les inclusions basées sur des chaînes, car elles ne sont pas sûres à l'exécution. Si le nom de la propriété de navigation change ou est mal orthographié, il sera interrompu. Suggère fortement d'utiliser à la place l'inclusion tapée.
Jeff Putz
2
depuis l'introduction de nameof (class), il est possible d'utiliser cette approche en toute sécurité. Dans le cas où le nom de l'entité change, il sera récupéré lors de la compilation. Exemple: context.Companies.Include (nameof (Employee)) Au cas où l'on aurait besoin d'aller plus bas, les noms doivent être concatents avec nameof (Employee) + "." + Nameof (Employee_Car)
Karl
La technique de la méthode d'extension ne fonctionne pas pour les requêtes compilées (du moins pas sur EFCore) confirmées ici: github.com/aspnet/EntityFrameworkCore/issues/7016
Dunge
156

EF 4.1 à EF 6

Il existe un type fortement typé.Include qui permet de spécifier la profondeur requise du chargement hâtif en fournissant des expressions Select à la profondeur appropriée:

using System.Data.Entity; // NB!

var company = context.Companies
                     .Include(co => co.Employees.Select(emp => emp.Employee_Car))
                     .Include(co => co.Employees.Select(emp => emp.Employee_Country))
                     .FirstOrDefault(co => co.companyID == companyID);

Le SQL généré dans les deux instances n'est toujours pas intuitif, mais semble suffisamment performant. J'ai mis un petit exemple sur GitHub ici

EF Core

EF Core a une nouvelle méthode d'extension .ThenInclude(), bien que la syntaxe soit légèrement différente :

var company = context.Companies
                     .Include(co => co.Employees)
                           .ThenInclude(emp => emp.Employee_Car)
                      ...

Selon la documentation, je garderais le «retrait» supplémentaire dans le .ThenIncludepour préserver votre santé mentale.

Informations obsolètes (ne faites pas cela):

Le chargement de plusieurs petits-enfants peut être effectué en une seule étape, mais cela nécessite une inversion plutôt maladroite du graphique avant de descendre le nœud suivant (NB: cela ne fonctionne PAS avec AsNoTracking()- vous obtiendrez une erreur d'exécution):

var company = context.Companies
         .Include(co => 
             co.Employees
                .Select(emp => emp.Employee_Car
                    .Select(ec => ec.Employee)
                    .Select(emp2 => emp2.Employee_Country)))
         .FirstOrDefault(co => co.companyID == companyID);

Je resterais donc avec la première option (un modèle de profondeur d'inclusion par entité feuille).

StuartLC
la source
4
Je me demandais comment le faire avec des instructions .Include fortement typées. Projeter les enfants avec Select était la réponse!
1
Mon équiv de "co.Employees.Select (...)" montre une erreur de syntaxe sur "Select", disant que "" Employés "ne contient pas de définition pour" Select "[ou méthode d'extension]". J'ai inclus System.Data.Entity. Je veux seulement obtenir une seule colonne de la table jointe.
Chris Walsh
1
J'avais une table parent qui faisait référence deux fois à la même table enfant. Avec l'ancienne syntaxe d'inclusion de chaîne, il était difficile de précharger la bonne relation. Cette manière est beaucoup plus spécifique. N'oubliez pas d'inclure l'espace de noms System.Data.Entity pour une inclusion fortement typée.
Karl
1
Avec .net core 2.1, j'avais besoin de l'espace de noms Microsoft.EntityFrameworkCore au lieu de System.Data.Entity
denvercoder9
27

Vous trouverez peut-être cet article intéressant disponible sur codeplex.com .

L'article présente une nouvelle façon d'exprimer des requêtes qui s'étendent sur plusieurs tables sous la forme de formes graphiques déclaratives.

De plus, l'article contient une comparaison approfondie des performances de cette nouvelle approche avec les requêtes EF. Cette analyse montre que GBQ surpasse rapidement les requêtes EF.

Merijn
la source
comment cela peut-il être implémenté dans une application réelle?
Victor.Uduak
0

Peut-être que cela aidera quelqu'un, 4 niveaux et 2 enfants à chaque niveau

Library.Include(a => a.Library.Select(b => b.Library.Select(c => c.Library)))
            .Include(d=>d.Book.)
            .Include(g => g.Library.Select(h=>g.Book))
            .Include(j => j.Library.Select(k => k.Library.Select(l=>l.Book)))
Shahid Islam
la source