为啥 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的主要内容,如果未能解决你的问题,请参考以下文章

为啥 EF Core 会生成倒位比较

如何在 .NET EF Core 中实现自引用多对多关系

如何将同一列添加到 EF Core 中的所有实体?

为啥 SelectMany 不适用于 EF Core Cosmos DB?

为啥 EF Core 2.0 会生成多个重复的 SQL 语句?

为啥 EF Core 在使用自定义 ValueGenerator 时尝试插入空值?