TGStat
TGStat
Введите текст для поиска
Расширенный поиск каналов
  • flag Russian
    Язык сайта
    flag Russian flag English flag Uzbek
  • Вход на сайт
  • Каталог
    Каталог каналов и чатов Региональные подборки Тематические подборки Платные каналы Поиск каналов
    Добавить канал/чат
  • Рейтинги
    Рейтинг каналов Рейтинг чатов Рейтинг публикаций
    Рейтинги брендов и персон
  • Аналитика
  • Поиск по публикациям
  • Мониторинг Telegram
  • Продвижение
    Реклама через Яндекс Бизнес Реклама в каналах через TGStat Agency Реклама на сайте TGStat.ru
НеСерьезный шарпист

2 Feb 2025, 16:42

Открыть в Telegram Поделиться Пожаловаться

Антипаттерн Lazy Loading

Lazy Loading является механизмом EF Core, с помощью которого неявно загружаются связанные сущности при первом обращении к их навигационным свойствам. Для того, чтобы EF Core мог использовать такой тип загрузки связанных данных, все навигационные свойства модели должны быть virtual.
public class Library
{
public int Id { get; set; }
public string Name { get; set; }

public virtual ICollection Books { get; set; }
}

А также при конфигурировании контекста необходимо вызвать UseLazyLoadingProxies(), включив поддержку прокси.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseLazyLoadingProxies() // Включение прокси для Lazy Loading
.UseNpgsql("Host=localhost;Database=mydb;");
}

Теперь необязательно при вытягивании Library (context.Library.FirstOrDefault()) необязательно явно делать получение Books через Include или Load, будет достаточно просто обратиться к данному свойству и получим необходимые данные.
Но как это работает, минусы будут, почему это антипаттерн, когда так все упрощается?!

Lazy Loading реализуется через динамические прокси-объекты, т.е. оно работает на основе наследования и переопределения виртуальных свойств. Когда мы запрашиваем сущность (context.Library.FirstOrDefault()), EF не возвращает саму сущность, а создаёт наследника от неё в рантайме!

Таким образом EF создаст динамический подкласс LibraryProxy, который наследуется от Library и переопределяет свойство Books таким образом, что при первом запросе автоматически выполняется sql-запрос к бд для загрузки данных в коллекцию (SELECT * FROM Books WHERE LibraryId = @Id), а при последующих данные будут доставаться уже из коллекции из памяти.

И к чему это может привести?! Как минимум, это уже неэффективно из-за того, что для генерации в рантайме таких прокси-классов EF использует Castle.DynamicProxy, использующий в свою очередь рефлексию, а использование виртуальных таблиц - это дополнительные расходы по памяти и по времени запроса. А как максимум, мы получаем кучу неявных обращений к БД (которые в ряде случаев нужно упаковать в один запрос), и потенциальную проблему N + 1 🤡.
⚠️ Проблема "N+1 запросов":
Если мы загружаем 10 Library и потом в цикле обращаемся к Books, это приведёт к 10 дополнительным запросам к БД. (А если таких сущностей 1000?))


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

🤡 Допустим, вам достался проект от предыдущей некомпетентной команды, которая взяла и втащила Lazy на глобальном уровне, и в куче мест получаем проблемы с производительностью. Что делать, как избавляться от данного говна?)

Как вариант, стоит договориться с командой в новом коде загружать данные только явным образом и для начала убрать Lazy на глобальном уровне и прописать его для всех моделей сущностей отдельно через Delegate-based Lazy Loading, что решит проблему с излишней кодогенерацией и позволит постепенно выпиливать Lazy для отдельных сущностей.
public class Library
{
private readonly ILazyLoader _lazyLoader;
private ICollection _books;

public Library() { }

public Blog(ILazyLoader lazyLoader)
{
_lazyLoader = lazyLoader;
}

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

public ICollection Books
{
get => _lazyLoader.Load(this, ref _books);
set => _books = value;
}
}

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

2k 0 20 4 30
Каталог
Каталог каналов и чатов Подборки каналов Поиск каналов Добавить канал/чат
Рейтинги
Рейтинг каналов Telegram Рейтинг чатов Telegram Рейтинг публикаций Рейтинги брендов и персон
API
API статистики API поиска публикаций API Callback
Наши каналы
@TGStat @TGStat_Chat @telepulse @TGStatAPI
Почитать
Академия TGStat Исследование Telegram 2019 Исследование Telegram 2021 Исследование Telegram 2023
Контакты
Справочный центр Поддержка Почта Вакансии
Всякая всячина
Пользовательское соглашение Политика конфиденциальности Публичная оферта
Наши боты
@TGStat_Bot @SearcheeBot @TGAlertsBot @tg_analytics_bot @TGStatChatBot