在 IQueryable 中按小时分组

Group by hour in IQueryable

在我的项目中,我整整 x 秒都从 SPS 接收了一些数据。每隔 y 分钟,我都会将当前数据存档到数据库中,以便能够显示统计信息。

我收到的数据被放入模型中。像这样但要复杂得多:

public class Data
{
    public DateTime ArchiveTime { get; set; }
    public float TempC { get; set; }
    public float CO2Percent { get; set; }
}

我有一个数据库存储库,returns 特定时间跨度内的所有条目。请参阅此代码:

// Context is my DbContext for a SQLite db and Data is the DbSet<Data> on that
IQueryable<Data> GetDataBetween(DateTime from, DateTime to) => Context.Data.Where(d => (d.ArchiveTime >= from && d.ArchiveTime <= to));

如您所见,returns 和 IQueryable 所以我想使用 linq to entities 功能。
我相信它被称为 linq to entities 但如果它不是,我的意思是将表达式树转换为 sql 或其他而不是仅在 C# 中执行它的功能。

由于数据库中每小时的条目数量无法确定,我想每小时只获取一个条目(第一个),这样我就可以在图表中显示它.

下面是一些日期时间的示例,可能更能体现我的意图:
注意:这些只是对象中包含的日期时间,我想要整个对象 - 而不仅仅是时间。

// say this is all the data I get between two times
2019-07-06 10:30:01 // I want
2019-07-06 10:40:09
2019-07-06 10:50:10
2019-07-06 11:00:13 // I want
2019-07-06 11:10:20
2019-07-06 11:20:22
2019-07-06 11:30:24
2019-07-06 11:40:32
2019-07-06 11:50:33
2019-07-06 12:00:35 // I want
2019-07-06 12:10:43
2019-07-06 12:20:45
2019-07-06 12:40:54
2019-07-06 12:50:56
2019-07-06 13:00:58 // I want
2019-07-06 13:11:06
2019-07-06 13:21:08
2019-07-06 13:31:09

我目前的做法是通过 IEnumerableGroupBy。请参阅此代码:

var now = DateTime.Now;
IQueryable<Data> dataLastWeek = repos.GetDataBetween(now.AddDays(-7), now);

IEnumerable<Data> onePerHour = dataLastWeek.AsEnumerable()
    .GroupBy(d => new DateTime(d.ArchiveTime.Year, d.ArchiveTime.Month, d.ArchiveTime.Day, d.ArchiveTime.Hour, 0, 0))
    .Select(g => g.First());

这很好用,但是因为它使用 IEnumerable 并创建对象,所以我没有得到 linq to entities 的优势,我认为这种方式肯定慢很多。

有什么方法可以重写此查询以在 SQLite 数据库上使用 IQueryable

编辑:我正在使用 .net core 3 preview6(最新预览)版本的 EF Core。也许有一个新功能可以实现我想要的:)

GroupBy 的关键部分可以很容易地通过避免 new DateTime(...) 和使用匿名类型

来翻译
.GroupBy(d => new { d.ArchiveTime.Date, d.ArchiveTime.Hour })

Date 属性 + AddHours:

.GroupBy(d => d.ArchiveTime.Date.AddHours(d.ArchiveTime.Hour))

不幸的是,目前 (EF Core 2.2) 不会将嵌套的 First / FirstOrDefault / Take(1) 转换为 SQL 并使用客户端评估。对于 First() 它是为了模拟 LINQ to Objects 抛出行为而强制执行的,但对于其他两种模式,它是由于缺乏正确的翻译造成的。

我为您的具体查询看到的唯一服务器端解决方案是根本不使用 GroupBy,而是相关的自反连接,如下所示:

var onePerHour = dataLastWeek.Where(d => !dataLastWeek.Any(d2 =>
    d2.ArchiveTime.Date == d.ArchiveTime.Date &&
    d2.ArchiveTime.Hour == d.ArchiveTime.Hour &&
    d2.ArchiveTime < d.ArchiveTime));