Как проверить производительность и предложения, чтобы сделать быстрее? - PullRequest
0 голосов
/ 30 октября 2018

Кажется, я написал какой-то очень медленный фрагмент кода, который становится медленнее, когда мне приходится иметь дело с 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 еще несколько столбцов, из которых мне нужна информация.

В области // создания 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, которые, вероятно, замедляют его.

Ответы [ 3 ]

0 голосов
/ 30 октября 2018

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

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

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

0 голосов
/ 01 ноября 2018

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

Я, очевидно, не могу проверить это, и я немного не уверен, куда деваются ваши спецификации, когда они у вас есть, но я предполагаю, что они становятся частью 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 вытянуть результаты в память за один раз.

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

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

0 голосов
/ 30 октября 2018

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

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

...