Entity Framework linq query Include () wiele jednostek podrzędnych

176

To może być naprawdę elementarne pytanie, ale jaki jest dobry sposób na uwzględnienie wielu jednostek podrzędnych podczas pisania zapytania obejmującego TRZY poziomy (lub więcej)?

czyli mam 4 tabel: Company, Employee, Employee_CariEmployee_Country

Firma ma relację 1: m z pracownikiem.

Pracownik ma relację 1: m zarówno z Employee_Car, jak i Employee_Country.

Jeśli chcę napisać zapytanie, które zwraca dane ze wszystkich 4 tabel, obecnie piszę:

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

Musi być bardziej elegancki sposób! To jest rozwlekłe i generuje horrendalny SQL

Używam EF4 z VS 2010

Nathan Liu
źródło

Odpowiedzi:

201

Użyj metod rozszerzających . Zastąp NameOfContext nazwą kontekstu obiektu.

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

}

Wtedy staje się twój kod

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

     //or if you want even more
     Company company = 
          context.CompanyById(companyID);
Nic
źródło
Ale chciałbym to wykorzystać w ten sposób: //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. Rozszerzenia powinny być pierwszym punktem wyjścia do ... cóż ... rozszerzenia predefiniowanej funkcjonalności.
Przyjdź
12
Wiele lat później nie polecałbym dołączeń opartych na ciągach, ponieważ nie są one bezpieczne w czasie wykonywania. Jeśli nazwa właściwości nawigacji kiedykolwiek się zmieni lub zostanie błędnie wpisana, zostanie przerwana. Zdecydowanie zasugeruj zamiast tego użycie wpisanego dołączenia.
Jeff Putz
2
od czasu wprowadzenia nameof (class) możliwe jest bezpieczne stosowanie tego podejścia. W przypadku zmiany nazwy jednostki, zostanie ona pobrana podczas kompilacji. Przykład: context.Companies.Include (nameof (Employee)) W przypadku, gdy trzeba przejść dalej, nazwy muszą być zgodne z nameof (Employee) + "." + Nameof (Employee_Car)
Karl
Technika metody rozszerzającej nie działa dla skompilowanych zapytań (przynajmniej nie na EFCore) potwierdzona tutaj: github.com/aspnet/EntityFrameworkCore/issues/7016
Dunge
156

EF 4.1 do EF 6

Istnieje silnie typizowany typ,.Include który umożliwia określenie wymaganej głębokości zachłannego ładowania przez podanie wyrażeń Select na odpowiednią głębokość:

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

Sql wygenerowany w obu przypadkach nadal nie jest intuicyjny, ale wydaje się wystarczająco wydajny. Umieściłem tutaj mały przykład na GitHubie

EF Core

EF Core ma nową metodę rozszerzenia .ThenInclude(), chociaż składnia jest nieco inna :

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

Zgodnie z dokumentacją, chciałbym zachować dodatkowe „wcięcie” w .ThenIncludecelu zachowania zdrowia psychicznego.

Przestarzałe informacje (nie rób tego):

Wczytanie wielu wnuków można wykonać w jednym kroku, ale wymaga to raczej niezręcznego odwrócenia wykresu przed przejściem do następnego węzła (uwaga: to NIE działa AsNoTracking()- pojawi się błąd w czasie wykonywania):

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

Więc pozostałbym przy pierwszej opcji (jeden model Uwzględnij na głębokość elementu liścia).

StuartLC
źródło
4
Zastanawiałem się, jak to zrobić z silnie wpisanymi instrukcjami .Include. Odpowiedzią była projekcja dzieci za pomocą Select!
1
Mój odpowiednik „co.Employees.Select (...)” pokazuje błąd składni w „Select”, mówiąc, że „Employees” nie zawiera definicji dla „Select” [lub metody rozszerzenia] ”. Dołączyłem System.Data.Entity. Chcę uzyskać tylko jedną kolumnę z połączonej tabeli.
Chris Walsh
1
Miałem tabelę nadrzędną, która dwukrotnie odwoływała się do tej samej tabeli podrzędnej. Ze starą składnią dołączania ciągów trudno było wstępnie załadować odpowiednią relację. Ten sposób jest o wiele bardziej szczegółowy. Pamiętaj, aby uwzględnić przestrzeń nazw System.Data.Entity dla silnie wpisanych dołączeń.
Karl
1
Z .net core 2.1 potrzebowałem przestrzeni nazw Microsoft.EntityFrameworkCore zamiast System.Data.Entity
denvercoder9
27

Może zainteresować Cię ten artykuł, który jest dostępny pod adresem codeplex.com .

W artykule przedstawiono nowy sposób wyrażania zapytań obejmujących wiele tabel w postaci deklaratywnych kształtów wykresów.

Ponadto artykuł zawiera dokładne porównanie wydajności tego nowego podejścia z zapytaniami EF. Ta analiza pokazuje, że GBQ szybko przewyższa zapytania EF.

Merijn
źródło
jak można to zaimplementować w rzeczywistej aplikacji?
Victor Uduak,