为啥 EF Core 最后要添加一个额外的 ORDER BY

Posted

技术标签:

【中文标题】为啥 EF Core 最后要添加一个额外的 ORDER BY【英文标题】:Why is EF Core adding an extra ORDER BY on the end为什么 EF Core 最后要添加一个额外的 ORDER BY 【发布时间】:2021-08-04 18:18:50 【问题描述】:

我有一个问题:

    public async Task<IEnumerable<CategoryDto>> GetCategoriesAsync()
    
        var dto = await _context.Categories.FromSqlRaw(
                @"SELECT * FROM [cp].[GetCategoryTree]")
            .ProjectTo<CategoryDto>(_mapper.ConfigurationProvider)
            .AsNoTrackingWithIdentityResolution()
            .ToListAsync();

        return dto;
    

它产生这个 sql:

SELECT [c].[banner_group_id], [c].[category_id], [c].[description], [c].[inactive], [c].[issitecategory], [c].[keywords], [c].[category_name], [c].[page_content], [c].[parent_category_id], [c].[sort_order], [c].[title], [b].[banner_group_id], [b].[category_id], [b].[description], [b].[inactive], [b].[issitecategory], [b].[keywords], [b].[category_name], [b].[page_content], [b].[parent_category_id], [b].[sort_order], [b].[title]

FROM (

    SELECT * FROM [cp].[GetCategoryTree]

) AS [c]

LEFT JOIN [bma_ec_categories] AS [b] ON [c].[category_id] = [b].[parent_category_id]

ORDER BY [c].[category_id], [b].[category_id]

[cp].[GetCategoryTree] 的输出已在该视图中排序。为什么 EF Core 最后要添加一个额外的ORDER BY?我可以告诉 EF Core 不排序吗?

【问题讨论】:

奇怪...是 Automapper 的 ProjectTo 吗?您可以删除它并记录您的查询吗? @GHDevOps 是的,就是这么做的。 映射到CategoryDto 导致加入,我看到当 EF-core 加入时它总是按 PK 值添加排序,可能是为了使后处理更容易(在内存中构建对象)。 视图没有排序,什么意思它已经排序了? 不,他们不能,这只是一个能够将单词ORDER BY 放在视图中的黑客,这仅意味着TOP 在排序后被评估,它没有 我重复 DOES NOT 对外部查询强制执行任何排序,这是一个完整的神话。见***.com/a/15188437/14868997和mssqltips.com/sqlservertip/4488/…和bengribaudo.com/blog/2015/05/01/3430/…和dba.stackexchange.com/a/21437/220697 【参考方案1】:

递归CTE返回结果是普通的记录集,也就是说如果你在客户端需要树,你必须自己重构它。

让我们重用原始查询:

public async Task<IEnumerable<CategoryDto>> GetCategoriesAsync()

    var plainResult = await _context.Categories.FromSqlRaw(@"SELECT * FROM [cp].[GetCategoryTree]")
        .Select(c => new CategoryDto
        
            CategoryId = c.CategoryId,
            ParentId = c.ParentId,
            Name = c.Name,
            SortOrder = c.SortOrder
        )
        .ToListAsync();

       var lookup = plainResult.Where(x => x.ParentId != 0).ToLookup(x => x.ParentId);

       foreach (c in plainResult)
       
           if (lookup.ContainsKey(c.CategoryId))
           
               c.Children = lookup[c.CategoryId]
                   .OrderBy(x => x.SortOrder)
                   .ThenBy(x => x.Name)
                   .ToList();
                       
       

    var dto = plainResult.Where(x => x.ParentId == 0).ToList();

    return dto;

【讨论】:

其实我在这里看不到为什么需要递归 CTE。您可以直接从_context.Categories 进行原始查询。如果要选择树的叶子,则需要 CTE。【参考方案2】:

我从一个平面 Category 查询开始。我需要它在树结构中。所以我为Children 添加了一个Include。但它只包括根元素上的子元素。我问了 SO here 并被指向 Recursive CTE。这就是[cp].[GetCategoryTree]。我现在已经放弃了所有这些并提出了我的问题:

    public async Task<IEnumerable<CategoryDto>> GetCategoriesAsync()
    
       var dto = await _context.Categories
            .Where(x => x.ParentId == 0)
            .OrderBy(x=> x.SortOrder)
            .ThenBy(x => x.Name)
            .ProjectTo<CategoryDto>(_mapper.ConfigurationProvider)
            .AsNoTracking()
            .ToListAsync();

       foreach (var categoryDto in dto)
       
           foreach (var categoryDtoChild in categoryDto.Children)
           
               categoryDtoChild.Children = await _context.Categories
                   .Where(x => x.ParentId == categoryDtoChild.CategoryId)
                   .OrderBy(x => x.SortOrder)
                   .ThenBy(x => x.Name)
                   .ProjectTo<CategoryDto>(_mapper.ConfigurationProvider)
                   .AsNoTracking()
                   .ToListAsync();
            
       

        return dto;
    

这很有效,慢慢确定,但一直填充到曾孙。如果有人有更好的解决方案,我会全力以赴。

基准测试结果(我的和@SvyatoslavDanyliv):

Method IterationCount Mean Error StdDev Median Min Max Ratio RatiosD Gen 0 Gen 1 Gen 2 Allocated
GetCategories 1 142.82 ms 5.260 ms 14.921 ms 142.65 ms 85.572 ms 170.29 ms 1.00 0.00 1000.0000 - - 5,845 KB
GetCategoriesSingleQuery 1 11.17 ms 0.501 ms 1.396 ms 10.87 ms 8.057 ms 14.76 ms 0.08 0.01 - - - 914 KB
GetCategories 2 290.03 ms 7.703 ms 21.473 ms 288.38 ms 239.879 ms 350.48 ms 1.00 0.00 2000.0000 - - 11,637 KB
GetCategoriesSingleQuery 2 23.21 ms 1.815 ms 5.207 ms 21.58 ms 17.005 ms 38.73 ms 0.08 0.02 - - - 1,745 KB

我想我会接受@SvyatoslavDanyliv 的回答。

【讨论】:

为什么不单查询? @SvyatoslavDanyliv 请给我一个单个查询的示例。 我已经添加了另一个答案。

以上是关于为啥 EF Core 最后要添加一个额外的 ORDER BY的主要内容,如果未能解决你的问题,请参考以下文章