TGViewer
НеСерьезный шарпист НеСерьезный шарпист @serious_seesharp · 2.4K subscribers
Post #75 3.08K
Антипаттерн 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<Book> 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<Book> _books;

public Library() { }

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

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

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


Однако такой подход не решает проблему N+1 и нужно все равно искать данные места и исправлять их, переводя загрузку сущностей на явную, а не через 100500 отдельных неявных запросов.
  • 👍 21
  • 🔥 7
  • ❤ 4
More from @serious_seesharp
  1. Jul 11, 2026Знакомый создал бота @booster_tbot, для прохождения мок-собесов бесплатно, а вопросы основ…
  2. Jun 30, 2025Всем, привет! Мы сейчас начинаем отбор докладов на осеннюю конфу "Стачка" в Питере. Если у…
  3. Jun 28, 2025Столкнулся с тем, что, как выяснилось, не всем очевидно для чего нужен ValueTask и когда е…
  4. Jun 5, 2025Post #85
  5. May 24, 2025Давно хотел сделать розыгрыш, и вот, кажется, время настало 🥰 Не надо никуда ничего скиды…
  6. May 23, 2025👩‍💻 Когда в C# 12 появились primary constructors (первичные конструкторы класс/структур)…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →