Кажется, я написал очень медленный фрагмент кода, который становится медленнее, когда мне приходится иметь дело с 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, которые, вероятно, замедляют его.
Вам повезло с ответами всех присутствующих?
Я не эксперт, но эта часть вашего второго 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))
, чтобы избежать зацикливания всех записей?
Во-первых, циклы внутри циклов - это очень плохо, вам следует реорганизовать их и сделать их одним циклом. Это не должно быть проблемой, потому что inventorySpecifications
объявлен вне цикла.
Во-вторых, строка var inventorySpecifications = dbContext.InventoryCategorySpecifications.Where(x => x.InventoryCategoryId == categoryId).Select(x => x.InventorySpecification);
должен заканчиваться на ToList()
, потому что его перечисления происходят во внутреннем foreach, что означает, что запрос выполняется для каждого из "inventoryItems"
это должно сэкономить вам много времени
Не могли бы вы развить свой первый пункт. Я действительно не вижу, как его извлечь, поскольку каждый элемент должен пройти и проверить.
Также для создания take my InventoryItemDto требуется 500 миллисекунд, и я считаю, что это очень медленно. Я не понимаю, почему, как я уже сказал, я с нетерпением жду загрузки.
«Петли внутри петель - это очень плохо ...» По какой причине?
циклы внутри циклов имеют сложность On ^ 2, что означает, что вы будете выполнять код внутреннего цикла экспоненциально много раз ... Я рекомендую всегда стараться избегать этого и реорганизовывать его каким-либо другим способом ...
проверь это: softwareengineering.stackexchange.com/questions/199196/…
Чтобы помочь вам лучше понять, что 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, поскольку доступ к свойствам, которые определены в коде, приведет к фрагментации запросов и выполняется в нескольких частях.
Чтобы помочь вам лучше понять, что EF работает за кулисами, добавьте некоторый вход в систему, чтобы показать выполняемый SQL, который может помочь вам увидеть, как / где ваши запросы идут не так. docs.microsoft.com/en-us/ef/core/miscellaneous/logging