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

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

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


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.


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)))
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.