GroupBy 与 EF Core 6.0 和 SQL Server

Posted

技术标签:

【中文标题】GroupBy 与 EF Core 6.0 和 SQL Server【英文标题】:GroupBy with EF Core 6.0 and SQL Server 【发布时间】:2021-12-28 02:35:00 【问题描述】:

我有一个 Blazor Web 应用程序已经在该领域工作了几个月。我想将数据库查询扩展到类似的“检测”组。

它是从 .NET 5 开始编写的,就在今天更新到 .NET 6 试图让它工作。

我想知道如何获得按TimeStamp(一个 DateTime 属性)排序的结果。我有一个内存数据库的工作示例,但生产将在 SQL Server 中。我在 SQL 方面不是很出色,但我在 Management Studio 中玩了一段时间,但没有运气。

正确注释掉 OrderByDescending() 分组,但结果顺序不正确。似乎 EF 翻译过程完全删除了该行,它对生成的查询或结果集没有影响。

var results = context.Detections
//Line below makes no change ignored by SQL Server. Works when using in memory DB.
    //.OrderByDescending(det => det.TimeStamp) 
    .GroupBy(det => new
    
        Year = det.TimeStamp.Year,
        Month = det.TimeStamp.Month,
        Day = det.TimeStamp.Day,
        Hour = det.TimeStamp.Hour,
    )
    .Select(grp => new
    
        Count = grp.Count(),
        Detection = grp.OrderByDescending(det => det.TimeStamp).First(),
    )
//The following line will not translate
    //.OrderByDescending(det => det.Detection.TimeStamp)
    .ToList();

如果有任何问题:

Visual Studio 2022 (4.8.04084) .Net 6.0 SQL Server 2019 (15.0.2080.9) *所有与 EF 相关的 NuGet 包均已更新至 6.0

编辑澄清 上述代码段产生以下 SQL 查询。

SELECT [t].[c], [t0].[Id], [t0].[TimeStamp]
FROM (
    SELECT COUNT(*) AS [c], DATEPART(year, [d].[TimeStamp]) AS [c0], DATEPART(month, [d].[TimeStamp]) AS [c1], DATEPART(day, [d].[TimeStamp]) AS [c2], DATEPART(hour, [d].[TimeStamp]) AS [c3]
    FROM [Detections] AS [d]
    WHERE [d].[TimeStamp] > DATEADD(day, CAST(-16.0E0 AS int), GETUTCDATE())
    GROUP BY DATEPART(year, [d].[TimeStamp]), DATEPART(month, [d].[TimeStamp]), DATEPART(day, [d].[TimeStamp]), DATEPART(hour, [d].[TimeStamp])
) AS [t]
OUTER APPLY (
    SELECT TOP(1) [d0].[Id], [d0].[TimeStamp]
    FROM [Detections] AS [d0]
    WHERE ([d0].[TimeStamp] > DATEADD(day, CAST(-30.0E0 AS int), GETUTCDATE())) AND (((([t].[c0] = DATEPART(year, [d0].[TimeStamp])) AND ([t].[c1] = DATEPART(month, [d0].[TimeStamp]))) AND ([t].[c2] = DATEPART(day, [d0].[TimeStamp]))) AND ([t].[c3] = DATEPART(hour, [d0].[TimeStamp])))
    ORDER BY [d0].[TimeStamp] DESC
) AS [t0]

它产生类似于以下的结果。通知未按时间排序。

1   628591  2021-11-02 14:34:06.0442966  
10  628601  2021-11-12 05:43:27.7015291  
150 628821  2021-11-12 21:59:27.6444236  
20  628621  2021-11-12 06:17:13.7798282  
50  628671  2021-11-12 15:17:23.8893856  

如果我在 Management Studio 中的 SQL 查询末尾添加 ORDER BY [t0].TimeStamp DESC,我会得到我正在寻找的结果(见下文)。我只需要知道如何在 LINQ 中编写它。

150 628821  2021-11-12 21:59:27.6444236  
50  628671  2021-11-12 15:17:23.8893856  
20  628621  2021-11-12 06:17:13.7798282  
10  628601  2021-11-12 05:43:27.7015291  
1   628591  2021-11-02 14:34:06.0442966  

ToList() 之前的末尾添加.OrderByDescending(det => det.Detection.TimeStamp) 是我的第一个想法,但“无法翻译”。我需要对这些结果进行分页,所以我真的很想在 SQL 中进行排序。

【问题讨论】:

【参考方案1】:

GroupBy 必须自己进行排序,这样“忽略”就不会完全出乎意料。

将其移至分组下方:

var results = context.Detections
    //.OrderByDescending(det => det.TimeStamp) 
    .GroupBy(det => new
    
        Year = det.TimeStamp.Year,
        Month = det.TimeStamp.Month,
        Day = det.TimeStamp.Day,
        Hour = det.TimeStamp.Hour,
    )
//    .OrderByDescending(grp => grp.Key)  // may have to split into y/m/d/h again
    .OrderByDescending(grp => grp.Key.Year)
       .ThenByDescending( grp => grp.Key.Month)
       .ThenByDescending( grp => grp.Key.Day)
       .ThenByDescending( grp => grp.Key.Hour)
    .Select(grp => new
    
        Count = grp.Count(),
        Detection = grp.OrderByDescending(det => det.TimeStamp).First(),
    )
    .ToList();

当 EF 支持它时,排序和分组可能会变得更容易

.GroupBy(det => new

    Date = det.TimeStamp.Date,
    Hour = det.TimeStamp.Hour,
)

【讨论】:

我想过这个。默认情况下,我按小时分组。使用这种方法,分钟、秒和毫秒仍然是无序的。我可以在内存中对它们进行排序,但是如果分页将小时分成两页怎么办? 我看到您从每个组中选择了一个 Count() 和一个 First()。怎么可能分开?并且 First() 之前的 grp.OrderByDescending 应该尊重分钟和秒。 目标是显示组中的最新检测以及该组中的检测数。该部分工作正常,并且确实尊重分钟和秒。我的问题是组显示顺序错误,我想先显示最新的。分组前的 OrderByDecsending 与直接 LING 和内存数据库一起使用。这似乎更像是 SQL Server 的限制。 你还是不清楚。但是.OrderByDescending(grp => grp.Key) 并不能真正起作用,您必须选择.OrderBy().ThenBy() 选项。 我添加了一些修改以进行澄清。很抱歉造成混淆,感谢您的帮助。我能够在 SQL 中得到结果,只是不知道如何让它在 LINQ 中工作。【参考方案2】:

对于任何在未来看到这个的人。

我能够通过声明和填充TimeStamp 属性并在最后使用OrderByDescending() 来完成这项工作。我不确定这是否是最好的解决方案,但它确实解决了我的问题。

var results = context.Detections
    .GroupBy(det => new
    
        Year = det.TimeStamp.Year,
        Month = det.TimeStamp.Month,
        Day = det.TimeStamp.Day,
        Hour = det.TimeStamp.Hour,
    )
    .Select(grp => new
    
        Count = grp.Count(),
        TimeStamp = grp.OrderByDescending(det => det.TimeStamp).First().TimeStamp,
        Detection = grp.OrderByDescending(det => det.TimeStamp).First(),
    )
    .OrderByDescending(det => det.TimeStamp)
    .ToList();

【讨论】:

以上是关于GroupBy 与 EF Core 6.0 和 SQL Server的主要内容,如果未能解决你的问题,请参考以下文章

在 EF Core 6.0 中测试表 IsTemporal

Asp.net Core 6.0 使用EF Model First 连接mysql

Asp.net Core 6.0 使用EF Model First 连接mysql

EF Core Linq-to-Sql GroupBy SelectMany 不适用于 SQL Server

EF Core group by 和 order by

EF 6.0 - 更新与 EntityState.Modified 的关系