如何对此进行性能测试并提出更快的建议?

时间:2018-10-30 15:15:24

标签: c# performance asp.net-core ef-core-2.1

我似乎写了一些非常慢的代码,当我不得不使用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循环。

3 个答案:

答案 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上,因为它们访问的是内部定义的属性。代码将导致查询碎片化并在多个部分中运行。