在我的项目中,我全x秒都从SPS接收了一些数据。每隔y分钟,我会将当前数据存档到一个数据库中,以便能够显示统计信息。
我收到的数据放入模型中。这样的东西,但要复杂得多:
public class Data
{
public DateTime ArchiveTime { get; set; }
public float TempC { get; set; }
public float CO2Percent { get; set; }
}
我有一个数据库存储库,该存储库在特定时间段内返回所有条目。看到以下代码:
// 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));
如您所见,这将返回IQueryable
,因此我想使用linq to实体功能。
我相信它被称为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
我目前的操作方式是通过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对实体的优势,我认为这样做的速度必须慢得多。
有什么方法可以重写此查询以在SQLite数据库上与IQueryable
一起使用?
编辑:我正在使用EF Core的.net Core 3 Preview6(最新预览)版本。也许有一项新功能可以满足我的需求:)
答案 0 :(得分:2)
通过避免使用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));