Платформа сущностей linq query Include () нескольких дочерних сущностей

176

Это может быть действительно элементарный вопрос, но какой хороший способ включить несколько дочерних объектов при написании запроса, который охватывает ТРИ уровня (или больше)?

то есть у меня есть 4 таблицы: Company, Employee, Employee_CarиEmployee_Country

Компания имеет отношения 1: m с Сотрудником.

Сотрудник имеет отношения 1: m с Employee_Car и Employee_Country.

Если я хочу написать запрос, который возвращает данные из всех 4 таблиц, я сейчас пишу:

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

Должен быть более элегантный способ! Это многословно и генерирует ужасный SQL

Я использую EF4 с VS 2010

Натан Лю
источник

Ответы:

201

Используйте методы расширения . Замените NameOfContext на имя вашего контекста объекта.

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

}

Тогда ваш код становится

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

     //or if you want even more
     Company company = 
          context.CompanyById(companyID);
шухер
источник
Но я хотел бы использовать это как: //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
Хамид
Буллси Никс. Расширения должны быть первым портом захода для ... ну ... расширения предопределенной функциональности.
ComeIn
12
Спустя годы я бы не советовал включать на основе строк, потому что они не безопасны во время выполнения. Если имя свойства навигации когда-либо изменится или будет написано с ошибкой, оно сломается. Настоятельно рекомендуем вместо этого использовать типизированное включение.
Джефф Путц
2
с момента введения nameof (class) можно безопасно использовать этот подход. В случае изменения имени объекта, оно будет выбрано во время компиляции. Пример: context.Companies.Include (nameof (Employee)) В случае, если нужно идти дальше вниз, имена должны объединяться с nameof (Employee) + "." + Nameof (Employee_Car)
Карл
Методика метод расширения не работает для скомпилированных запросов (по крайней мере , не на EFCore) подтвердил здесь: github.com/aspnet/EntityFrameworkCore/issues/7016
Dunge
156

EF 4.1 до EF 6

Существует строго типизированный тип,.Include который позволяет задавать необходимую глубину активной загрузки, предоставляя выражения Select на соответствующую глубину:

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, сгенерированный в обоих случаях, отнюдь не интуитивно понятен, но кажется достаточно производительным. Я положил небольшой пример на GitHub здесь

EF Core

EF Core имеет новый метод расширения .ThenInclude(), хотя синтаксис немного отличается :

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

Согласно документам, я бы сохранил дополнительный «отступ», .ThenIncludeчтобы сохранить ваше здравомыслие.

Устаревшая информация (не делайте этого):

Многократная загрузка внуков могла бы быть сделана за один шаг, но это требует довольно неловкого обращения к графику, прежде чем отправиться вниз по следующему узлу (NB: Это НЕ работает AsNoTracking()- вы получите ошибку времени выполнения):

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

Таким образом, я бы остановился на первом варианте (один параметр «Включить для каждой модели глубины сущности»).

StuartLC
источник
4
Мне было интересно, как это сделать с помощью строго типизированных операторов .Include. Проектирование детей с помощью Select было ответом!
1
Мой эквивалент «co.Employees.Select (...)» показывает синтаксическую ошибку «Select», говоря, что «Employees» не содержит определения «Select» [или метода расширения] ». Я включил System.Data.Entity. Я хочу получить только один столбец из объединенной таблицы.
Крис Уолш
1
У меня была родительская таблица, которая дважды ссылалась на одну и ту же дочернюю таблицу. Со старой синтаксисом включения строки было трудно предварительно установить правильные отношения. Этот способ намного более конкретен. Пожалуйста, имейте в виду, чтобы включить пространство имен System.Data.Entity для строго типизированного включения.
Карл
1
В ядре .net 2.1 мне требовалось пространство имен Microsoft.EntityFrameworkCore вместо System.Data.Entity
denvercoder9
27

Вы можете найти эту интересную статью, которая доступна на codeplex.com .

В статье представлен новый способ выражения запросов, охватывающих несколько таблиц, в форме декларативных графовых форм.

Кроме того, статья содержит подробное сравнение производительности этого нового подхода с запросами EF. Этот анализ показывает, что GBQ быстро превосходит запросы EF.

Merijn
источник
как это можно реализовать в реальном приложении?
Victor.Uduak
0

Может быть, это поможет кому-то, 4 уровня и 2 ребенка на каждом уровне

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)))
Шахид Ислам
источник