以下LINQ代码
var param = Expression.Parameter(typeof(Measure), "x");
var args = new List<SqlExpression>
{
new SqlConstantExpression(Expression.Constant(TimeSpan.FromMinutes(15)), new TimeSpanTypeMapping("interval")),
new SqlFragmentExpression("time")
};
var timeBucket = new SqlFunctionExpression(
"time_bucket_gapfill";
args,
false,
args.Select(_ => false),
typeof(DateTimeOffset),
new DateTimeOffsetTypeMapping("timestamp with time zone")
);
var keySelector = Expression.Lambda<Func<Measure, DateTimeOffset>>(timeBucket, param);
_dbContext
.Measures
.Where(x => x.Time > start && x.Time < end)
.GroupBy(
KeySelector,
(timeSample, measures) =>
new KeyValuePair<DateTimeOffset, double>(
timeSample,
measures.Average(x => x.Value)
)
).Take(10);
生成以下SQL:
-- @__p_0='10'
SELECT t0."Key", avg(t0.value::double precision)
FROM (
SELECT t.value, time_bucket_gapfill('00:15:00', time) AS "Key"
FROM measures AS t
WHERE t.time > TIMESTAMPTZ '2010-01-01 00:00:00+00:00' AND t.time < TIMESTAMPTZ '2010-02-01 00:00:00+00:00'
) AS t0
GROUP BY t0."Key"
LIMIT @__p_0
EOF
问题是函数
time_bucket_gapfill
必须放置在具有时间限制的GROUP BY和WHERE子句的相同(子)查询中。所以上面生成的查询不起作用:我需要的是
-- @__p_0='10'
SELECT time_bucket_gapfill('00:15:00', time) AS "Key", avg(t.value::double precision)
FROM measures AS t
WHERE t.time > TIMESTAMPTZ '2010-01-01 00:00:00+00:00' AND t.time < TIMESTAMPTZ '2010-02-01 00:00:00+00:00'
GROUP BY "Key"
LIMIT @__p_0
EOF
我找不到用 EF 实现此目的的方法,部分原因是在数据库集上使用
FromSql
,我不想这样做。
我虽然想手动创建一个
SelectExpression
,但是没有关于这个领域的文档。
我将 Entity Framework Core 7.0.3 与 PostgreSQL 一起使用。
更新
为了有一些上下文,我添加了一些关于时间尺度的信息:它不会影响问题,但会激发为什么上面两个查询不同。
measures
是一个 Timescale hypertable(即我调用了 SELECT create_hypertable('measures', 'time');
)。
SQL 函数
time_bucket
返回记录的 time bucket:它采用 time
列并将其四舍五入到最接近的时间样本(由给定的时间间隔决定)。
time_bucket_gapfill
为每个没有记录的时间桶添加一条空记录。此函数要正常工作,必须在同一个(子)查询的 SELECT 中调用,其中有 GROUP BY 和 WHERE 子句限制时间:只有这样它才能知道填补空白的时间间隔.
MWE
public class Measure
{
public DateTimeOffset Time { get; set; }
public double Value { get; set; }
protected BaseMeasure(DateTimeOffset time, double value)
{
Time= time;
Value = value;
}
}
public class MeasureConfiguration
{
public override void Configure(EntityTypeBuilder<Measure> builder)
{
builder.ToTable("measures");
builder.HasKey(x => x.Time);
builder.Property(x => x.Time)
.HasColumnName("time");
builder.Property(x => x.Value)
.HasColumnName("value");
}
}
public sealed class MyDbContext : DbContext
{
public DbSet<Measure> Measures { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new MeasureConfiguration());
}
}
表中不需要数据:如果工作正常,gapfill 将返回空时间桶。
运行EF迁移后,执行如下SQL:
SELECT create_hypertable('measures', 'time');
然后 EF 生成的查询(上面第一个)将不会返回任何内容;相反,第二个查询将从 2010-01-01 到 2010-02-01 每 15 分钟返回一条记录,其中值列为空。