在 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
我目前的做法是通过 IEnumerable
和 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());
这很好用,但是因为它使用 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));
在我的项目中,我整整 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
我目前的做法是通过 IEnumerable
和 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());
这很好用,但是因为它使用 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));