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


Безотложная загрузка связанных данных

Безотложная загрузка

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

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

Совет

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

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

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

Внимание

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

Включение нескольких уровней

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

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

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

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

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

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

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

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

Совет

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

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

Включение с фильтрацией

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

Поддерживаются операции: Where, OrderBy, OrderByDescending, ThenBy, ThenByDescending, Skip и Take.

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

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

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

using (var context = new BloggingContext())
{
    var filteredBlogs = 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))
        .ToList();
}

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

using (var context = new BloggingContext())
{
    var filteredBlogs = 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))
        .ToList();
}

Внимание

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

Пример:

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

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

Примечание.

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

Использование метода Include с производными типами

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

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

    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 = context.Themes.ToList();
}

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

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

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

Примечание.

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