Анализ медленной отложенной загрузки в Entity Framework 6 - PullRequest
0 голосов
/ 25 сентября 2019

У меня есть класс POCO с отложенной загрузкой коллекции, которая в настоящее время загружается за 10 секунд и содержит около 10.000 записей;используя Entity Framework 6.3 и SQL Server 2016.

Я понимаю, что загрузка 10.000 записей занимает некоторое время, и, возможно, такое время следует ожидать.Но я не могу понять, где на самом деле тратится время.

Я знаю, что могу отключить отложенную загрузку.Но сейчас я просто хочу лучше понять эту проблему.Как узнать, где теряется время и какие точные операторы SQL занимают много времени или выполняются слишком часто?


Загрузка свойства, например, через Console.WriteLine(parent.Children.First().ID), занимает 10 секунд.

Если я включаю ведение журнала через dbContext.Database.Log = s => Console.WriteLine(s);, я вижу только одну команду SQL:

SELECT
    [Extent1].[Foo] AS [Foo], ...
    [Extent1].[Parent_ID] AS [Parent_ID],
    FROM [dbo].[Child] AS [Extent1]
    WHERE [Extent1].[Parent_ID] = @EntityKeyValue1

-- Completed in 1 ms with result: SqlDataReader

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

Когда я запрашиваю базу данных в SQL Management Studio, я получаю аналогичное время: отображение всех 10.000 строк занимает всего несколько миллисекунд.

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

Свойство настроено так:

public class Parent {
    ...
    public virtual ICollection<Children> Children { get; set; }
}
public class Child {
    ...
    public int Parent_ID { get; set; }
    public virtual Parent Parent{ get; set; }
}

...
modelBuilder.Entity<Child>()
            .HasRequired(a => a.Parent)
            .WithMany(b => b.Children)
            .HasForeignKey(c => c.Parent_ID)
            .WillCascadeOnDelete(false);

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

1 Ответ

1 голос
/ 25 сентября 2019

Когда 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.Данные можно суммировать, сортировать, разбивать на страницы, как вам угодно.

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

...