Как это проверить на производительность и предложения по ускорению?

Кажется, я написал очень медленный фрагмент кода, который становится медленнее, когда мне приходится иметь дело с EF Core.

В основном у меня есть список элементов, которые хранят атрибуты в строке Json в базе данных, поскольку я храню много разных элементов с разными атрибутами.

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

Это довольно медленно при выполнении 700 записей примерно за 18-30 секунд (с того места, где я запускаю свой таймер, а не весь блок кода).

var itemDtos = new List<ItemDto>();


            var inventoryItems = dbContext.InventoryItems.Where(x => x.InventoryCategoryId == categoryId);

            var inventorySpecifications = dbContext.InventoryCategorySpecifications.Where(x => x.InventoryCategoryId == categoryId).Select(x => x.InventorySpecification);



            Stopwatch a = new Stopwatch();
            a.Start();

            foreach (var item in inventoryItems)
            {
                var specs = JObject.Parse(item.Attributes);
                var specDtos = new List<SpecDto>();

                foreach (var inventorySpecification in inventorySpecifications.OrderBy(x => x.DisplayOrder))
                {
                    if (specs.ContainsKey(inventorySpecification.JsonKey))
                    {

                        var value = specs.GetValue(inventorySpecification.JsonKey);

                        var newSpecDto = new SpecDto()
                        {
                            Key = inventorySpecification.JsonKey,
                            Value = displaySpec.ToString()
                        };

                        specDtos.Add(newSpecDto);

                    }
                }

                var dto = new InventoryItemDto()
                {
                    // create dto
                };

                inventoryItemDtos.Add(dto);
            }

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

В области // create dto я получаю некоторую информацию из других таблиц

     var dto = new InventoryItemDto()
           {
               // access brand columns
               // access company columns
               // access branch columns
               // access country columns
               // access state columns
           };

При попытке доступа к этим столбцам в цикле требуется 6 минут для обработки 700 строк.

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

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

   var inventoryItems = dbContext.InventoryItems.Include(x => x.Branch).ThenInclude(x => x.Company)
                                                    .Include(x => x.Branch).ThenInclude(x => x.Country)
                                                    .Include(x => x.Branch).ThenInclude(x => x.State)
                                                    .Include(x => x.Brand)
                                                    .Where(x => x.InventoryCategoryId == categoryId).ToList();

поэтому я подумал, что из-за этого скорость не будет сильно отличаться от исходной 18-30 секунд.

Я также хотел бы ускорить исходный код, но я не совсем уверен, как избавиться от двойных циклов foreach, которые, вероятно, замедляют его.

Чтобы помочь вам лучше понять, что EF работает за кулисами, добавьте некоторый вход в систему, чтобы показать выполняемый SQL, который может помочь вам увидеть, как / где ваши запросы идут не так. docs.microsoft.com/en-us/ef/core/miscellaneous/logging

Henry 01.11.2018 15:01

Вам повезло с ответами всех присутствующих?

Henry 08.11.2018 12:29
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
60
3

Ответы 3

Я не эксперт, но эта часть вашего второго foreach поднимает красный флаг: inventorySpecifications.OrderBy(x => x.DisplayOrder). Поскольку это вызывается внутри другого foreach, он выполняет вызов .OrderBy каждый раз, когда вы перебираете inventoryItems.

Перед первым циклом foreach попробуйте следующее: var orderedInventorySpecs = inventorySpecifications.OrderBy(x => x.DisplayOrder);, а затем используйте foreach (var inventorySpec in orderedInventorySpecs) и посмотрите, имеет ли это значение.

Вы также можете отфильтровать результаты от orderedInventorySpec, например orderedInventorySpecs.Where(x => specs.ContainsKey(inventorySpecification.JsonKey)), чтобы избежать зацикливания всех записей?

Simply Ged 30.10.2018 23:38

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

Во-вторых, строка
var inventorySpecifications = dbContext.InventoryCategorySpecifications.Where(x => x.InventoryCategoryId == categoryId).Select(x => x.InventorySpecification);
должен заканчиваться на ToList(), потому что его перечисления происходят во внутреннем foreach, что означает, что запрос выполняется для каждого из "inventoryItems"

это должно сэкономить вам много времени

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

chobo2 30.10.2018 17:39

Также для создания take my InventoryItemDto требуется 500 миллисекунд, и я считаю, что это очень медленно. Я не понимаю, почему, как я уже сказал, я с нетерпением жду загрузки.

chobo2 30.10.2018 18:15

«Петли внутри петель - это очень плохо ...» По какой причине?

Kenneth K. 01.11.2018 16:26

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

Leonardo 01.11.2018 17:53

проверь это: softwareengineering.stackexchange.com/questions/199196/…

Leonardo 01.11.2018 18:15

Чтобы помочь вам лучше понять, что EF работает за кулисами, добавьте некоторый вход в систему, чтобы показать выполняемый SQL, который может помочь вам увидеть, как / где ваши запросы идут не так. Это может быть чрезвычайно полезно для определения того, не слишком ли часто ваши запросы попадают в БД. Как правило, вы хотите обращаться к БД как можно меньше раз и получать только ту информацию, которая вам нужна, с помощью .Select (), чтобы уменьшить количество возвращаемых данных. Документация для ведения журнала: http://docs.microsoft.com/en-us/ef/core/miscellaneous/logging

Я, очевидно, не могу это проверить, и я немного не уверен, куда идут ваши specDto, когда они у вас есть, но я предполагаю, что они станут частью InventoryItemDto?

var itemDtos = new List<ItemDto>();

var inventoryItems = dbContext.InventoryItems.Where(x => x.InventoryCategoryId == categoryId).Select(x => new InventoryItemDto() {
    Attributes = x.Attributes,
    //.....
    // access brand columns
   // access company columns
   // access branch columns
   // access country columns
   // access state columns
}).ToList();


var inventorySpecifications = dbContext.InventoryCategorySpecifications
.Where(x => x.InventoryCategoryId == categoryId)
.OrderBy(x => x.DisplayOrder)
.Select(x => x.InventorySpecification).ToList();



foreach (var item in inventoryItems)
{
    var specs = JObject.Parse(item.Attributes);
    // Assuming the specs become part of an inventory item?
    item.specs = inventorySpecification.Where(x => specs.ContainsKey(x.JsonKey)).Select(x => new SpecDto() { Key = x.JsonKey, Value = specs.GetValue(x.JsonKey)});
}

Первый вызов БД для inventoryItems должен произвести один SQL-запрос, который сразу извлечет всю информацию, которая вам нужна для создания InventoryItemDto, и, таким образом, попадет в БД только один раз. Затем он извлекает спецификации и использует OrderBy () перед материализацией, что означает, что OrderBy будет запускаться как часть запроса SQL, а не в памяти. Оба этих результата материализуются с помощью .ToList (), что заставляет EF загружать результаты в память за один раз.

Наконец, цикл перебирает ваши созданные inventoryItems, анализирует Json и затем фильтрует спецификации на основе этого. Я не уверен, где вы использовали specDtos, поэтому я предположил, что это часть модели. Я бы порекомендовал проверить производительность работы Json, которую вы выполняете, поскольку это может способствовать замедлению вашей работы.

Более интегрированный подход к использованию Json как части ваших моделей EF можно увидеть в этом ответе: https://stackoverflow.com/a/51613611/621524, однако вы все равно не сможете использовать эти свойства для разгрузки выполнения в SQL, поскольку доступ к свойствам, которые определены в коде, приведет к фрагментации запросов и выполняется в нескольких частях.

Другие вопросы по теме