Поиск предметов, относящихся к определенной категории

У меня есть служба элементов, которая вызывается из контроллера API ядра Asp.Net. Запрос получает все элементы в определенной категории для отображения на веб-сайте электронной коммерции (reactjs). Контроллер возвращает только json:

    public async Task<IEnumerable<EcommerceItemDto>> GetAllItemsAsync(string customerNumber, string category = "All", int page = 0, int pageSize = 9999)
    {
        IQueryable<Category> categories;

        if (category == "All")
        {
            categories = _context.Categories
                .Include(c => c.Children)
                .Include(p => p.Parent)
                .AsNoTrackingWithIdentityResolution();
        }
        else
        {
            categories = _context.Categories
                .Where(n => n.Name == category)
                .Include(c => c.Children)
                .Include(p => p.Parent)
                .AsNoTrackingWithIdentityResolution();
        }

        var items = await _context.EcommerceItems
            .FromSqlInterpolated($"SELECT * FROM [cp].[GetEcommerceItemsView] WHERE [CustomerNumber] = {customerNumber}")
            .Include(x => x.Category)
            .Include(i => i.Images.OrderByDescending(d => d.Default))
            .OrderBy(i => i.ItemNumber)
            .Where(c => categories.Any(x => x.Children.Contains(c.Category)) || categories.Contains(c.Category))
            .Skip(page * pageSize)
            .Take(pageSize)
            .AsNoTracking()
            .ToListAsync();

        var dto = _mapper.Map<IEnumerable<EcommerceItemDto>>(items);

        return dto;
    }

Я могу показать свой контроллер Action, [cp].[GetEcommerceItemsView] Вид, EcommerceItem Учебный класс, EcommerceItemDto Учебный класс, Category Класс и Images Класс, если кто думает, что поможет. Я сбежал Tuning в моей базе данных Sql и применил рекомендации. Я отметил это, используя _mapper.Map а также .ProjectTo и mapper.Map быстрее на волосок. В моей тестовой базе данных всего около 65 элементов с изображениями. Но этот запрос занимает от 1000 до 1500 мс.

ОБНОВИТЬ

По запросу @ iSR5 …

public class EcommerceItem : INotifyPropertyChanged
{
    [Key]
    public string ItemNumber { get; set; }

    public string ItemDescription { get; set; }

    [Column(TypeName = "text")]
    public string ExtendedDesc { get; set; }
    
    public bool Featured { get; set; }

    public string CategoryName { get; set; }

    public Category Category { get; set; }

    [Column(TypeName = "numeric(19, 5)")]
    public decimal Price { get; set; }

    [Column(TypeName = "numeric(19, 5)")]
    public decimal QtyOnHand { get; set; }

    public bool? Metadata1Active { get; set; }
    
    [StringLength(50)]
    public string Metadata1Name { get; set; }
    
    [StringLength(50)]
    public string Metadata1 { get; set; }
    
    ... // I have Twenty Matadata Groups

    public bool? Metadata20Active { get; set; }
    
    [StringLength(50)]
    public string Metadata20Name { get; set; }
    
    [StringLength(50)]
    public string Metadata20 { get; set; }

    public ICollection<EcommerceItemImages> Images { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Мой EcommerceItemDto:

public class EcommerceItemDto
{
    [Key]
    public string ItemNumber { get; set; }

    public string ItemDescription { get; set; }

    [Column(TypeName = "text")]
    public string ExtendedDesc { get; set; }

    public bool Featured { get; set; }

    public string CategoryName { get; set; }

    [Column(TypeName = "numeric(19, 5)")]
    public decimal Price { get; set; }
    
    [Column(TypeName = "numeric(19, 5)")]
    public decimal QtyOnHand { get; set; }

    public bool? Metadata1Active { get; set; }
    
    [StringLength(50)]
    public string Metadata1Name { get; set; }
    
    [StringLength(50)]
    public string Metadata1 { get; set; }

    ... // Same as EcommerceItem I have twenty Metadata groups
    
    public bool? Metadata20Active { get; set; }
    
    [StringLength(50)]
    public string Metadata20Name { get; set; }
    
    [StringLength(50)]
    public string Metadata20 { get; set; }
    
    public ICollection<EcommerceItemImagesDto> Images { get; set; }
}

Моя модель категории:

[Table("bma_ec_categories")]
public class Category : INotifyPropertyChanged
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    [Column("category_id")]
    public int CategoryId { get; set; }

    [Column("parent_category_id")]
    public int? ParentId { get; set; }

    [Required]
    [Column("category_name")]
    [StringLength(50)]
    public string Name { get; set; }

    public Category Parent { get; set; }

    public ICollection<Category> Children { get; set; }
    
    [Column("title")] 
    [StringLength(150)] 
    public string Title { get; set; }

    [Column("description")]
    [StringLength(250)]
    public string Description { get; set; }

    [Column("keywords")]
    [StringLength(250)]
    public string Keywords { get; set; }

    [Column("page_content", TypeName = "text")]
    public string PageContent { get; set; }

    [Column("banner_group_id")] 
    public int? BannerGroupId { get; set; }

    [Column("inactive")] 
    public byte? Inactive { get; set; }

    [Column("issitecategory")] 
    public byte Issitecategory { get; set; }

    [Column("metadata1_active")] 
    public byte Metadata1Active { get; set; }

    [Column("metadata1_name")]
    [StringLength(50)]
    public string Metadata1Name { get; set; }

    ...

    [Column("metadata20_active")] 
    public byte? Metadata20Active { get; set; }

    [Column("metadata20_name")]
    [StringLength(50)]
    public string Metadata20Name { get; set; }

    [Column("sort_order")] 
    public int? SortOrder { get; set; }

    public ICollection<EcommerceItem> Items { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Просто чтобы быть полным, мой View, он получает рассчитанную цену для вошедшего в систему пользователя и выравнивает метаданные категории и элемента:

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

ALTER VIEW [cp].[GetEcommerceItemsView]
AS
SELECT
    RTRIM(LTRIM(IV.ITEMNMBR)) AS ItemNumber
   ,RTRIM(LTRIM(IM.ITEMDESC)) AS ItemDescription
   ,ecItems.[extended_desc] AS ExtendedDesc
   ,CAST(ecItems.Featured AS BIT) AS Featured
   ,cat.category_name AS CategoryName
   ,CASE IM.PRICMTHD
        WHEN 1 THEN IV.UOMPRICE
        WHEN 2 THEN IV.UOMPRICE * IC.LISTPRCE / 100
        WHEN 3 THEN (IM.CURRCOST) * (1 + (IV.UOMPRICE / 100))
        WHEN 4 THEN (IM.STNDCOST) * (1 + (IV.UOMPRICE / 100))
        WHEN 5 THEN (IM.CURRCOST) / (1 - (IV.UOMPRICE / 100))
        WHEN 6 THEN (IM.STNDCOST) / (1 - (IV.UOMPRICE / 100))
        ELSE 0
    END AS Price
   ,IQ.QTYONHND AS QtyOnHand
   ,CAST(cat.[metadata1_active] AS BIT) AS [Metadata1Active]
   ,cat.[metadata1_name] AS [Metadata1Name]
   ,ecItems.[metadata1] AS [Metadata1]

   ...

   ,CAST(cat.[metadata20_active] AS BIT) AS [Metadata20Active]
   ,cat.[metadata20_name] AS [Metadata20Name]
   ,ecItems.[Metadata20] AS [Metadata20]
   ,C.CUSTNMBR AS CustomerNumber
FROM dbo.RM00101 AS C
LEFT OUTER JOIN dbo.IV00108 AS IV
    ON C.PRCLEVEL = IV.PRCLEVEL
LEFT OUTER JOIN dbo.IV00101 AS IM
    ON IM.ITEMNMBR = IV.ITEMNMBR
LEFT OUTER JOIN dbo.IV00102 AS IQ
    ON IQ.ITEMNMBR = IV.ITEMNMBR
        AND IQ.RCRDTYPE = 1
LEFT OUTER JOIN dbo.IV00105 AS IC
    ON IC.ITEMNMBR = IV.ITEMNMBR
        AND IV.CURNCYID = IC.CURNCYID
LEFT OUTER JOIN dbo.bma_ec_items AS ecItems
    ON ecItems.ITEMNMBR = IV.ITEMNMBR
LEFT OUTER JOIN dbo.bma_ec_item_category AS icat
    ON icat.ITEMNMBR = IV.ITEMNMBR
LEFT OUTER JOIN dbo.bma_ec_categories AS cat
    ON cat.category_id = icat.category_id
WHERE (IM.ITEMNMBR IN (SELECT
        ITEMNMBR
    FROM dbo.bma_ec_items
    WHERE (display_on_ecommerce = 1))
)
GO

Специальное примечание: Я использую сериализатор Newtonsoft Json в своем контроллере. Я знаю, что существуют более быстрые сериализаторы.

ОБНОВЛЕНИЕ 2

В соответствии с запросом, следующие результаты теста:

BenchmarkDotNet = v0.13.0, ОС = Windows 10.0.19043.1110 (21H1 / May2021Update) Intel Core i7-7700 CPU 3,60 ГГц (Kaby Lake), 1 процессор, 8 логических и 4 физических ядра .NET SDK = 5.0.302
[Host] : .NET 5.0.8 (5.0.821.31504), X64 RyuJIT [AttachedDebugger]
DefaultJob: .NET 5.0.8 (5.0.821.31504), X64 RyuJIT

МетодIterationCountИметь в видуОшибкаStdDevМин.МаксимумСоотношениеСоотношение SD
GetItems50228,2 мс8,15 мс23,65 мс119,7 мс265,6 мс1,000,00
GetItemsWithChanges50231,0 мс4,60 мс10,66 мс211,3 мс253,1 мс1.040,22
GetItems100455,8 мс9,04 мс24,29 мс414,9 мс522,0 мс1,000,00
GetItemsWithChanges100453,8 мс9.00 мс20,32 мс421,0 мс502,2 мс1,000,08

ОБНОВЛЕНИЕ 3

Тест без категорий:

МетодIterationCountИметь в видуОшибкаStdDevМин.МаксимумСоотношениеСоотношение SD
GetItems50231,1 мс6.06 мс17,88 мс140,4 мс259,7 мс1,000,00
GetItemsWithChangesWithoutCategories50230,9 мс4,61 мс10,69 мс214,0 мс255,4 мс1.010,15
GetItems100450,2 мс8,99 мс22,40 мс416,6 мс510,1 мс1,000,00
GetItemsWithChangesWithoutCategories100452,5 мс9.01 мс23,10 мс417,3 мс515,9 мс1.010,08

1 ответ
1

Я выполнил все рекомендации из RedGate Sql Prompt (изменил свое мнение на одно рекомендуемое изменение):

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

ALTER VIEW [cp].[GetEcommerceItemsView]
AS
SELECT
    RTRIM(LTRIM(IV.ITEMNMBR)) AS ItemNumber
   ,RTRIM(LTRIM(IM.ITEMDESC)) AS ItemDescription
   ,ecItems.[extended_desc] AS ExtendedDesc
   ,CAST(ecItems.Featured AS BIT) AS Featured
   ,cat.category_name AS CategoryName
   ,CASE IM.PRICMTHD
        WHEN 1 THEN IV.UOMPRICE
        WHEN 2 THEN IV.UOMPRICE * IC.LISTPRCE / 100
        WHEN 3 THEN (IM.CURRCOST) * (1 + (IV.UOMPRICE / 100))
        WHEN 4 THEN (IM.STNDCOST) * (1 + (IV.UOMPRICE / 100))
        WHEN 5 THEN (IM.CURRCOST) / (1 - (IV.UOMPRICE / 100))
        WHEN 6 THEN (IM.STNDCOST) / (1 - (IV.UOMPRICE / 100))
        ELSE 0
    END AS Price
   ,IQ.QTYONHND AS QtyOnHand
   ,CAST(cat.[metadata1_active] AS BIT) AS [Metadata1Active]
   ,cat.[metadata1_name] AS [Metadata1Name]
   ,ecItems.[metadata1] AS [Metadata1]

   ...   

   ,CAST(cat.[metadata20_active] AS BIT) AS [Metadata20Active]
   ,cat.[metadata20_name] AS [Metadata20Name]
   ,ecItems.[Metadata20] AS [Metadata20]
   ,C.CUSTNMBR AS CustomerNumber
FROM dbo.RM00101 AS C
LEFT OUTER JOIN dbo.IV00108 AS IV
    ON C.PRCLEVEL = IV.PRCLEVEL
LEFT OUTER JOIN dbo.IV00101 AS IM
    ON IM.ITEMNMBR = IV.ITEMNMBR
LEFT OUTER JOIN dbo.IV00102 AS IQ
    ON IQ.ITEMNMBR = IV.ITEMNMBR
        AND IQ.RCRDTYPE = 1
LEFT OUTER JOIN dbo.IV00105 AS IC
    ON IC.ITEMNMBR = IV.ITEMNMBR
        AND IV.CURNCYID = IC.CURNCYID
LEFT OUTER JOIN dbo.bma_ec_items AS ecItems
    ON ecItems.ITEMNMBR = IV.ITEMNMBR 
    AND ecItems.display_on_ecommerce = 1
LEFT OUTER JOIN dbo.bma_ec_item_category AS icat
    ON icat.ITEMNMBR = IV.ITEMNMBR
LEFT OUTER JOIN dbo.bma_ec_categories AS cat
    ON cat.category_id = icat.category_id
GO

Повторно запустите советник по настройке и примените созданные индексы. Затем я переключился на System.Text.Json из Newtonsoft.Json в качестве сериализатора в проекте Asp.net Core. Проведите повторный тест производительности:

МетодIterationCountИметь в видуОшибкаStdDevМин.МаксимумСоотношениеСоотношение SD
GetItems50225,5 мс4,89 мс14,26 мс134,77 мс250,5 мс1,000,00
GetItemsWithChangesWithoutCategories50225,4 мс6,86 мс20,23 мс98,31 мс254,2 мс1.010,13
GetItems100434,6 мс7,65 мс11,21 мс415,40 мс460,2 мс1,000,00
GetItemsWithChangesWithoutCategories100442,1 мс8,78 мс16,50 мс418,91 мс478,6 мс1.020,05

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

ОБНОВИТЬ

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

МетодIterationCountИметь в видуОшибкаStdDevМин.МаксимумСоотношение
GetItems506.260 с0,1213 с0,1490 с6.012 с6.598 с1,00
GetItemsWithChangesWithoutCategories502,036 с0,0407 с0,0543 с1.948 с2,162 с0,32
GetItems10012,521 с0,2439 с0,2281 с12.048 с12.932 с1,00
GetItemsWithChangesWithoutCategories1004.135 с0,0790 с0,0739 с4.031 с4.242 с0,33

  • Не думаю, что щегольство решит проблему. Вам необходимо повторно оценить представление вне вашего кода (используя SSMS или любую IDE СУБД), а затем применить к нему тот же запрос, пока Execution Plan горит. затем просмотрите план выполнения и посмотрите, что можно улучшить. Что касается обновленного теста (с исправленным именем пользователя), он показывает, что он даже становится медленнее, чем раньше. (возможно, из-за получения дополнительных данных после того, как вы исправили имя пользователя).

    — iSR5

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *