Поделиться через


Страстная загрузка связанных данных

Предварительная загрузка

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

using (var context = new BloggingContext())
{
    var blogs = await context.Blogs
        .Include(blog => blog.Posts)
        .ToListAsync();
}

Подсказка

Entity Framework Core автоматически исправит свойства навигации для любых других сущностей, которые ранее были загружены в экземпляр контекста. Таким образом, даже если вы явно не включаете данные для свойства навигации, свойство может быть заполнено, если некоторые или все связанные сущности были загружены ранее.

В один запрос можно включить связанные данные из нескольких связей.

using (var context = new BloggingContext())
{
    var blogs = await context.Blogs
        .Include(blog => blog.Posts)
        .Include(blog => blog.Owner)
        .ToListAsync();
}

Предостережение

При загрузке навигации по коллекции в одном запросе могут возникнуть проблемы с производительностью. Дополнительные сведения см. в разделе "Одинарные и разделенные запросы".

Включая несколько уровней

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

using (var context = new BloggingContext())
{
    var blogs = await context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .ToListAsync();
}

Можно цепочкой вызовов к ThenInclude продолжить интеграцию дополнительных уровней связанных данных.

using (var context = new BloggingContext())
{
    var blogs = await context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .ThenInclude(author => author.Photo)
        .ToListAsync();
}

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

using (var context = new BloggingContext())
{
    var blogs = await context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .ThenInclude(author => author.Photo)
        .Include(blog => blog.Owner)
        .ThenInclude(owner => owner.Photo)
        .ToListAsync();
}

Может потребоваться включить несколько связанных сущностей для одной из включенных сущностей. Например, при запросе Blogs, необходимо включить Posts, а затем включить как Author, так и Tags из Posts. Чтобы включить оба, необходимо указать каждый путь подключения, начиная с корневого уровня. Например, Blog -> Posts -> Author и Blog -> Posts -> Tags. Это не означает, что вы получите избыточные соединения; В большинстве случаев EF объединяет соединения при создании SQL.

using (var context = new BloggingContext())
{
    var blogs = await context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Tags)
        .ToListAsync();
}

Подсказка

Вы также можете загрузить несколько навигаций с помощью одного Include метода. Это возможно для "цепочек" навигации, которые состоят исключительно из ссылок или когда они заканчиваются одной коллекцией.

using (var context = new BloggingContext())
{
    var blogs = await context.Blogs
        .Include(blog => blog.Owner.AuthoredPosts)
        .ThenInclude(post => post.Blog.Owner.Photo)
        .ToListAsync();
}

Фильтрованное включение

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

Поддерживаемые операции: Where, OrderBy, OrderByDescending, ThenBy, ThenByDescending, Skipи Take.

Такие операции должны применяться к навигации по коллекции в передаваемой в метод Include лямбда-функции, как показано в следующем примере:

using (var context = new BloggingContext())
{
    var filteredBlogs = await context.Blogs
        .Include(
            blog => blog.Posts
                .Where(post => post.BlogId == 1)
                .OrderByDescending(post => post.Title)
                .Take(5))
        .ToListAsync();
}

Каждая включенная навигация разрешает только один уникальный набор операций фильтрации. В случаях, когда для заданной навигации по коллекции применяются несколько операций включения (blog.Posts в примерах ниже), операции фильтрации можно указать только в одном из них:

using (var context = new BloggingContext())
{
    var filteredBlogs = await context.Blogs
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
        .ToListAsync();
}

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

using (var context = new BloggingContext())
{
    var filteredBlogs = await context.Blogs
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
        .ToListAsync();
}

Предостережение

В случае запросов отслеживания результаты отфильтрованного включения могут быть непредвиденными из-за исправления навигации. Все соответствующие сущности, которые были запрошены ранее и сохранены в средстве отслеживания изменений, будут присутствовать в результатах запроса filtered Include, даже если они не соответствуют требованиям фильтра. Рассмотрите возможность использования NoTracking запросов или повторного создания DbContext в тех ситуациях, когда применяется фильтруемое включение.

Пример:

var orders = await context.Orders.Where(o => o.Id > 1000).ToListAsync();

// customer entities will have references to all orders where Id > 1000, rather than > 5000
var filtered = await context.Customers.Include(c => c.Orders.Where(o => o.Id > 5000)).ToListAsync();

Замечание

В случае запросов отслеживания навигация, к которой применена фильтрация, считается загруженной. Это означает, что EF Core не попытается повторно загрузить значения с помощью явной загрузки или отложенной загрузки, даже если некоторые элементы по-прежнему могут быть отсутствуют.

Включить производные типы

Можно включить связанные данные из навигации, определенные только в производном типе с помощью Include и ThenInclude.

Учитывая следующую модель:

public class SchoolContext : DbContext
{
    public DbSet<Person> People { get; set; }
    public DbSet<School> Schools { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<School>().HasMany(s => s.Students).WithOne(s => s.School);
    }
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Student : Person
{
    public School School { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<Student> Students { get; set; }
}

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

  • Использование приведения

    context.People.Include(person => ((Student)person).School).ToList()
    
  • Использование as оператора

    context.People.Include(person => (person as Student).School).ToList()
    
  • Использование перегрузки Include, принимающей параметр типа string

    context.People.Include("School").ToList()
    

Конфигурация модели для автоматического включения навигаций

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

modelBuilder.Entity<Theme>().Navigation(e => e.ColorScheme).AutoInclude();

После приведенной выше конфигурации выполнение запроса, как показано ниже, загрузит ColorScheme навигацию для всех тем в результатах.

using (var context = new BloggingContext())
{
    var themes = await context.Themes.ToListAsync();
}

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

Если для конкретного запроса вы не хотите загружать связанные данные с помощью навигации, которая на уровне модели настроена на автоматическое включение, можно использовать метод IgnoreAutoIncludes в запросе. Использование этого метода остановит загрузку всех навигаций, настроенных как автоматическое включение пользователем. Выполнив запрос, как показано ниже, вы получите все темы из базы данных, хотя ColorScheme не будет загружен, даже если он настроен для автоматического включения в навигацию.

using (var context = new BloggingContext())
{
    var themes = await context.Themes.IgnoreAutoIncludes().ToListAsync();
}

Замечание

Навигации к собственным типам также настраиваются как автоматически включенные по умолчанию, и использование API IgnoreAutoIncludes не препятствует их включению. Они по-прежнему будут включены в результаты запроса.