Entity Framework 避免由 GroupBy 创建的子查询

问题描述 投票:0回答:0

以下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
列并将其四舍五入到最接近的时间样本(由给定的时间间隔决定)。

此外,SQL函数

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 分钟返回一条记录,其中值列为空。

c# postgresql entity-framework entity-framework-core timescaledb
© www.soinside.com 2019 - 2024. All rights reserved.