Entity Framework - включает несколько уровней свойств

Метод Include() хорошо работает для списков объектов. Но что, если мне нужно пройти два уровня? Например, метод ниже вернет ApplicationServers с включенными свойствами, показанными здесь. Однако ApplicationsWithOverrideGroup - это другой контейнер, который содержит другие сложные объекты. Могу ли я сделать функцию Include() для этого свойства? Или как я могу получить это свойство для полной загрузки?

В нынешнем виде этот метод:

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

Заполняет только свойство Enabled (ниже), а не свойства Application или CustomVariableGroup (ниже). Как мне это сделать?

public class ApplicationWithOverrideVariableGroup : EntityBase
{
    public bool Enabled { get; set; }
    public Application Application { get; set; }
    public CustomVariableGroup CustomVariableGroup { get; set; }
}

10 ответов

Решение

Для EF 6

using System.Data.Entity;

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

См. Примечания для большего количества примеров.

Обязательно добавьте using System.Data.Entity; чтобы получить версию Include это берет в лямбду.


Для EF Core

Используйте новый метод ThenInclude

query.Include(x => x.Collection)
     .ThenInclude(x => x.Property);

Если я вас правильно понимаю, вы спрашиваете о включении вложенных свойств. Если так:

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

или же

.Include("ApplicationsWithOverrideGroup.NestedProp")  

или же

.Include($"{nameof(ApplicationsWithOverrideGroup)}.{nameof(NestedProp)}")  

EF Core: использование "ThenInclude" для загрузки нескольких уровней: например:

var blogs = context.Blogs
    .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .ThenInclude(author => author.Photo)
    .ToList();

Дополнительные примеры EFCore на MSDN показывают, что вы можете делать довольно сложные вещи с Include а также ThenInclude,

Это хороший пример того, насколько сложным вы можете стать (это все одно утверждение!):

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

Посмотрите, как вы можете цепи Include даже после ThenInclude и это как бы "сбрасывает" вас обратно на уровень сущности верхнего уровня (Инструкторы).

Вы даже можете повторять одну и ту же коллекцию "первого уровня" (CourseAssignments) несколько раз, а затем отдельные ThenIncludes Команды, чтобы добраться до разных дочерних объектов.

Обратите внимание, что ваш фактический запрос должен быть помечен в конце Include или же ThenIncludes цепь. Следующее НЕ работает:

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

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

Настоятельно рекомендую настроить ведение журнала и убедиться, что ваши запросы не вышли из-под контроля, если вы включаете более одной или двух вещей. Важно увидеть, как это работает на самом деле - и вы заметите, что каждое отдельное "включение" обычно является новым запросом, чтобы избежать массивных объединений, возвращающих избыточные данные.

AsNoTracking может значительно ускорить процесс, если вы не собираетесь редактировать сущности и сохранять их заново.

Я сделал небольшой помощник для Entity Framework 6 (стиль.Net Core), чтобы красиво включать под-сущности.

Сейчас на 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();

Пакет доступен на GitHub.

Я также должен был использовать несколько включений, и на 3-м уровне мне нужно было несколько свойств

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

Это может кому-то помочь:)

Позвольте мне четко заявить, что вы можете использовать строковую перегрузку для включения вложенных уровней независимо от кратности соответствующих отношений, если вы не возражаете против использования строковых литералов:

query.Include("Collection.Property")

Я придумал самый простой способ. Вам не нужно устанавливать пакет ThenInclude.EF или использовать ThenInclude для всех вложенных свойств навигации. Просто сделайте так, как показано ниже, EF позаботится обо всем остальном. пример:

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

Я собираюсь добавить свое решение к моей конкретной проблеме. Мне нужно было включить две коллекции одного уровня. Окончательное решение выглядело так.

var recipe = _bartendoContext.Recipes
    .Include(r => r.Ingredients)
    .ThenInclude(r => r.Ingredient)
    .Include(r => r.Ingredients)
    .ThenInclude(r => r.MeasurementQuantity)
    .FirstOrDefault(r => r.Id == recipeId);
if (recipe?.Ingredients == null) return 0m;
var abv = recipe.Ingredients.Sum(ingredient => ingredient.Ingredient.AlcoholByVolume * ingredient.MeasurementQuantity.Quantity);
return abv;

Это расчет процентного содержания алкоголя в данном рецепте напитка. Как вы можете видеть, я просто дважды включил сборник ингредиентов, а затем добавил туда ингредиент и количество.

У меня есть страница индекса, на которой отображается MbsNavigation.Name, представляющий собой объект микропрограммы, загруженный как внешний ключ. Объект Firmware большой, поэтому загрузка страницы Index через Интернет занимает несколько минут.

      BatterySystem = await _context.BatterySystems.Include(b => b.MbsNavigation)

Это решение для загрузки только Firmware.Name:

      BatterySystem = await _context.BatterySystems
.Include(b => b.MbsNavigation)
.Select(b => new BatterySystem() 
{
    Name = b.Name,
    MbsNavigation = new Firmware() { Name = b.MbsNavigation.Name },
})
.ToListAsync();

Теперь индекс загружается сразу.

Другие вопросы по тегам