DevGang
Авторизоваться

Загрузка данных в Entity Framework

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

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

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

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

Реализация (по пакету Proxies)

Мы увидим, как реализовать ленивую загрузку в EF Core:

Первый способ заключается в установке Proxy Package, предоставленного Microsoft. Все, что вам нужно сделать, это установить пакет Microsoft.EntityFrameworkCore.Proxies, который добавит все необходимые прокси-серверы, необходимые для запуска отложенной загрузки, и активирует ее с помощью вызова UseLazyLoadingProxies.

Это может быть достигнуто либо в методе OnConfiguring вашего класса Context:

using Microsoft.EntityFrameworkCore;

namespace DataLoading;

internal class MyContext : DbContext
{
    protected override void OnConfiguring
       (DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseLazyLoadingProxies()
            .UseInMemoryDatabase(databaseName: "MyDb");
    }
}

или при использовании AddDbContext скорее всего в Program.cs:

builder.Services.AddDbContext<MyContext>(
    b => b.UseLazyLoadingProxies()
          .UseInMemoryDatabase("MyDb"));

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

public class Author
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Book> Books { get; set; }
}

public class Book
{
    public int Id { get; set; }
    public int AuthorId { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public virtual Author Author { get; set; }
}

В приведенном выше примере мы используем ключевое слово virtual, чтобы пометить свойство Books как виртуальное, указывая, что оно должно загружаться лениво. Когда мы извлекаем авторов из базы данных, Entity Framework будет генерировать SQL-запросы для извлечения только авторов, а не книг. Когда мы обращаемся к свойству Book для конкретного Author, Entity Framework генерирует другой SQL-запрос для извлечения соответствующих книг.

Реализация (службой ILazyLoader)

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

Давайте посмотрим на пример:

public class Author
{
    private ICollection<Book> _books_;
    public Author()
    {
    }
    private Author(ILazyLoader lazyLoader)
    { 
        LazyLoader = lazyLoader;
    }

    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Book> Books
    { 
        get => LazyLoader.Load(this, ref _books); 
        set => _books = value; 
    }
}

public class Book
{
    private Author _author;

    public Book()
    {
    }
    private Book(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    } 
    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }
    public int AuthorId { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public virtual Author Author
    { 
        get => LazyLoader.Load(this, ref _author);
        set => _author = value;
    }
}

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

Теперь, когда вы запрашиваете сущность Author, EF сгенерирует SQL-запрос, который выглядит следующим образом:

SELECT [Id], [Name]
FROM [Authors]
WHERE [Id] = @id

Этот запрос выбирает только столбцы Id и Name из таблицы Authors, поскольку EF знает, что ему пока не нужно загружать коллекцию Books.

Затем, когда вы получите доступ к свойству Books в сущности Author, EF сгенерирует другой SQL-запрос для загрузки связанных сущностей Book:

SELECT [Id], [Name], [Price], [AuthorId] 
FROM [Books] 
WHERE [AuthorId] = @authorId

Аналогично, когда вы запрашиваете сущность Book, EF сгенерирует SQL-запрос, который выглядит следующим образом:

SELECT [Id], [Name], [Price], [AuthorId] 
FROM [Books] 
WHERE [Id] = @id

Этот запрос выбирает только столбцы Id, Name, Price и AuthorID из таблицы Books, поскольку EF знает, что ему пока не нужно загружать связанную сущность Author.

Затем, когда вы получите доступ к свойству Author в сущности Book, EF сгенерирует другой SQL-запрос для загрузки связанного объекта Author:

SELECT [Id], [Name] 
FROM [Authors] 
WHERE [Id] = @authorId

Этот запрос выбирает столбцы Id и Name из таблицы Authors, что является всем, что необходимо для заполнения свойства Author в сущности Book.

Нетерпеливая загрузка

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

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

Реализация

Вот пример того, как реализовать нетерпеливую загрузку с использованием Entity Framework на C#:

// To load authors with eager loading
using (var context = new MyContext())
{
    var authors = context.Authors.Include(author => author.Books).ToList();
    foreach (var author in authors)
    {
        Console.WriteLine(authors.Name);
        foreach (var book in author.Books)
        {
            Console.WriteLine(book.Name);
        }
    }
}   

Метод Include используется в приведенном выше примере, чтобы выразить желание загрузить свойство Books для каждого Author. Таблицы Authors и Books объединены Entity Framework для предоставления единого SQL-запроса, возвращающего всю необходимую информацию.

SELECT [a].[Id], [a].[Name], [b].[Id], [b].[Name], [b].[Price], [b].[AuthorId]
FROM [Authors] AS [a]
LEFT JOIN [Books] AS [b] ON [a].[Id] = [b].[AuthorId]
WHERE [a].[Id] = @authorId

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

using (var context = new MyContext())
{
    var authors = context.Authors
        .Include(author => author.Books)
        .ThenInclude(book => book.CoverImage)
        .Include(author => author.ContactDetails)
        .ToList();
} 

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

modelBuilder.Entity<Author>().Navigation(author => author.Books).AutoInclude();

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

using (var context = new MyContext()) 
{ 
    var authors = context.Authors.IgnoreAutoIncludes().ToList(); 
}

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

Явная загрузка

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

При явной загрузке вы используете метод Load объекта CollectionEntry или ReferenceEntry для явной загрузки связанных сущностей. Объект CollectionEntry представляет свойство навигации по коллекции в сущности, а объект ReferenceEntry представляет свойство навигации по ссылке в сущности.

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

Отложенная загрузка должна быть включена, чтобы явная загрузка работала, потому что она зависит от нее. Вызов Load для объекта CollectionEntry или ReferenceEntry не имеет никакого эффекта, если не включена отложенная загрузка.

Реализация

Давайте посмотрим пример и SQL-запрос, сгенерированный EF Core:

var author = dbContext.Authors.Find(authorId);
dbContext.Entry(author).Collection(a => a.Books).Load();

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

SELECT [a].[Id], [a].[Name]
FROM [Authors] AS [a]
WHERE [a].[Id] = @authorId

Когда Load вызывается для объекта CollectionEntry для коллекции Books, Entity Framework создает отдельный запрос для извлечения связанных сущностей Book:

SELECT [b].[Id], [b].[Name], [b].[Price], [b].[AuthorId]
FROM [Books] AS [b]
WHERE [b].[AuthorId] = @authorId

Вывод

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

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

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

#Начинающим #Data Science
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу