IQueryable 中按小时分组

Joe*_*ius 3 c# datetime linq-to-entities iqueryable entity-framework-core

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

我收到的数据被放入模型中。像这样但更复杂的事情:

public class Data
{
    public DateTime ArchiveTime { get; set; }
    public float TempC { get; set; }
    public float CO2Percent { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我有一个数据库存储库,它返回特定时间范围内的所有条目。看这段代码:

// 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));
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这会返回一个,IQueryable所以我想利用 linq toEntity 功能。
我相信它被称为实体的 linq,但如果不是,我指的是将表达式树转换为 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
Run Code Online (Sandbox Code Playgroud)

我目前的方法是通过 aIEnumerable和GroupBy。看这段代码:

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());
Run Code Online (Sandbox Code Playgroud)

这工作得很好,但由于它使用IEnumerable和创建对象,我没有得到 linq 到实体的优势,并且我认为这种方式一定会慢很多。

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

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

Iva*_*oev 5

通过避免和使用任一匿名类型,可以轻松地使关键部分GroupBy变得可翻译new DateTime(...)

.GroupBy(d => new { d.ArchiveTime.Date, d.ArchiveTime.Hour })
Run Code Online (Sandbox Code Playgroud)

或Date属性+ AddHours:

.GroupBy(d => d.ArchiveTime.Date.AddHours(d.ArchiveTime.Hour))
Run Code Online (Sandbox Code Playgroud)

不幸的是,目前(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));
Run Code Online (Sandbox Code Playgroud)