我似乎写了一些非常慢的代码,当我不得不使用EF Core时,它变得越来越慢。
基本上,我有一个将属性存储在数据库的Json字符串中的项目列表,因为我正在存储许多具有不同属性的不同项目。
然后我还有另一个表,其中包含每个属性的显示顺序,因此,当我将项目发送给客户端时,我将根据该顺序对其进行排序。
在大约18-30秒内(从我启动计时器的位置开始,而不是整个代码段)做700条记录有点慢。
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循环。
答案 0 :(得分:2)
首先,内部循环是一件很糟糕的事情,您应该将其重构并使其成为一个循环。这应该没有问题,因为inventorySpecifications
是在循环外部声明的
第二行,
var inventorySpecifications = dbContext.InventoryCategorySpecifications.Where(x => x.InventoryCategoryId == categoryId).Select(x => x.InventorySpecification);
应该以{{1}}结尾,因为它的枚举发生在内部foreach中,这意味着查询正在针对每个“ inventoryItems”运行
应该可以节省大量时间
答案 1 :(得分:0)
我不是专家,但是您第二个foreach的这一部分提出了一个危险信号:inventorySpecifications.OrderBy(x => x.DisplayOrder)
。因为这是在另一个foreach中调用的,所以每次您迭代.OrderBy
时,它都会进行inventoryItems
调用。
在您的第一个foreach循环之前,请尝试以下操作:var orderedInventorySpecs = inventorySpecifications.OrderBy(x => x.DisplayOrder);
,然后使用foreach (var inventorySpec in orderedInventorySpecs)
,看看是否有所不同。
答案 2 :(得分:0)
为帮助您更好地了解幕后运行的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)});
}
对库存物品的数据库的第一次调用应产生一个SQL查询,该查询将立即提取您构造InventoryItemDto所需的所有信息,因此仅命中数据库一次。然后,它撤消规范并在实现之前使用OrderBy(),这意味着OrderBy将作为SQL查询的一部分而不是在内存中运行。这两个结果都通过.ToList()实现,这将导致EF将结果一次性存储到内存中。
最后,循环遍历了您构造的清单项,解析了Json,然后基于此筛选规范。我不确定您在何处使用specDtos,因此我假设它是模型的一部分。我建议检查您正在执行的Json工作的性能,因为这可能会导致您的速度降低。
在此答案中可以看到将Json用作EF模型的一部分的更集成的方法:https://stackoverflow.com/a/51613611/621524,但是您仍然无法使用这些属性来将执行的工作卸载到SQL上,因为它们访问的是内部定义的属性。代码将导致查询碎片化并在多个部分中运行。