Когда EF необходимо получить большой набор данных, загруженных как с нетерпением, так и с отложенной загрузкой, ему нужно не только выполнить запрос SQL, но затем выделить эти объекты и разрешить любые потенциальные ссылки, содержащиеся в каждом из этих объектов, на любые отслеженныесущности, о которых он уже знает.Я подозреваю, что суть времени, которое вы наблюдаете, может быть из-за циклической ссылки Ребенка на Родителя.Когда вы «трогаете» коллекцию Children на родительском объекте, прокси-сервер EF запускает и запускает SELECT * from Children WHERE ParentId = 1
, который выполняется довольно быстро.Затем он отправляется и начинает выделять эти 10 тыс. Записей, и для каждой он проверяет любые ссылки на ссылки на известную отслеживаемую сущность.Поскольку родительский номер 1 отслеживается, каждый из этих объектов должен быть подключен к родительскому объекту.Очевидно, что если ваша модель включает больше ссылок, она будет проверять каждую из них на каждом объекте по своему кешу, чтобы назначить их, если она есть.Это требует времени.
Лучший совет, который я могу дать:
Не используйте циклические ссылки, если они вам действительно не нужны.Если вы хотите, чтобы у родителей был конкретный ребенок, вы все равно можете сделать это через Родителя без циркулярной ссылки.
- Избегайте ленивой загрузки в целом.Используйте проекцию с
Select
и энергичной загрузкой, где это необходимо.Ленивая загрузка может и приведет к различным проблемам.Один серьезный - когда имеешь дело с коллекциями.Например, если вы должны были загрузить подмножество дочерних элементов:
.
var children = context.Children.Where(x => x.Date > start && x.Date < end);
, которое вернуло 100 дочерних элементов, а затем имело некоторый код, который пошёл для сериализацииэта коллекция, что в конечном итоге произойдет, будет вызывать до 100 отложенных вызовов SQL, по 1 на каждого уникального родителя.Сериализатор коснется первого потомка, затем SELECT * FROM Parents WHERE ParentId = 31
, затем коснется второго потомка, SELECT * FROM Parents Where ParentId = 12
, затем третьего, затем четвертого и так далее.Каждая ссылка в каждой строке может вызвать запрос SQL.В отличие от энергичной нагрузки, которая использовала бы JOIN для возврата данных Child и Parent.С более сложными объектами это может быть абсолютным убийцей.Даже простое отключение кода при ленивой загрузке при оценке логики по отношению к набору сущностей может быть убийственным, когда код находится в производстве и существует несколько одновременных запросов.
Ленивая загрузка - интересная концепция, но опасная с точки зрения производительности.Проецирование на ViewModels / анонимные типы поможет избежать многочисленных головных болей в будущем.
Например, если вам нужны все 10 тыс. Дочерних элементов, но нужно только несколько свойств для отправки в представление или выполнения вычислений ...
var parent = context.Parents.Where(x => x.ParentId == parentId)
.Select( new
{
x.ParentId,
x.ParentName,
Children = x.Children.Select(c => new
{
c.ChildId,
c.ChildName
}).ToList()
}).Single();
Это очень простой пример, который загружает родительский идентификатор и имя вместе с простым списком дочерних элементов (идентификатора и имени) в анонимные типы.Если вам нужно что-то, что может быть возвращено представлению или вызову API, определите и заполните класс POCO ViewModel / DTO.Данные можно суммировать, сортировать, разбивать на страницы, как вам угодно.
Преимущество здесь в том, что вы выполняете более быстрый запрос, потому что выбираются необходимые данные и только те данные, которые вам нужны.Это может лучше использовать индексы для извлечения необходимых данных.Вы также избегаете любых проблем сериализации / циклических ссылок или отложенных загрузок других объектов, на которые ссылаются.Вы также минимизируете объем памяти, необходимый серверу и клиентам для представления результатов, и сокращаете объем данных по сети.