我需要按日期时间分组,但在特定时区使用 EF Core 和 PostgreSQL

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

我需要按其

CreatedAt
属性对交易进行分组。但在特定时区。

像这样

SELECT 
    SUM("Amount") 
FROM 
    public."Transactions" 
GROUP BY 
    CAST("CreatedAt" AS date) AT TIME ZONE 'America/Chicago'

但使用 EF Core。

我已经看到了 Mehdi 的这种方法,并尝试针对 PostgreSql 修改它。

这是我所拥有的 - 表达式生成器:

public class AtTimeZoneExpression : SqlFunctionExpression
{
    private readonly IReadOnlyCollection<SqlExpression> _params;

    public AtTimeZoneExpression(IReadOnlyCollection<SqlExpression> parameters)
        : base("notimportant", true, typeof(DateTimeOffset), RelationalTypeMapping.NullMapping)
    {
        _params = parameters;
    }

    protected override Expression Accept(ExpressionVisitor visitor)
    {
        if (visitor is not QuerySqlGenerator sqlGenerator)
            return base.Accept(visitor);

        if (_params.First().TypeMapping.DbType == System.Data.DbType.DateTimeOffset)
            visitor.Visit(new SqlFragmentExpression("CAST( "));

        visitor.Visit(_params.First());

        if (_params.First().TypeMapping.DbType == System.Data.DbType.DateTimeOffset)
            visitor.Visit(new SqlFragmentExpression(" as date)"));

        visitor.Visit(new SqlFragmentExpression(" AT TIME ZONE "));
        visitor.Visit(_params.Skip(1).First());

        return this;
    }

    protected override void Print([NotNull] ExpressionPrinter expressionPrinter)
    {
        expressionPrinter.Append("AT TIME ZONE Expression");
    }
}

功能:

public static class QueryHelper
{
    public static DateTimeOffset ToTimeZone(this DateTimeOffset source, string timeZone)
    {
        var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
        var date = TimeZoneInfo.ConvertTimeFromUtc(source.UtcDateTime, tz);
        return new DateTimeOffset(date, tz.GetUtcOffset(date));
    }
}

模型构建器:

modelBuilder.HasDbFunction(typeof(QueryHelper).GetMethod(nameof(QueryHelper.ToTimeZone), new[] { typeof(DateTimeOffset), typeof(string) }))
            .HasTranslation(args => new AtTimeZoneExpression(args));

及用法:

var activityPerDay = await transactionQuery
            .GroupBy(t => t.CreatedAt.ToTimeZone(timeZoneId).Date)
            .Select(dayGroup => new ItemMetrics
            {
                DateTime = dayGroup.Key,
                Volume = dayGroup.Sum(t => t.Amount ?? 0)
            })
            .ToListAsync(cancellationToken);

我使用的是 EF Core 版本 8.0.10。

这就是我得到的 t => t.CreatedAt.ToTimeZone(timeZoneId).Date

CreatedAt
是不可为空的
DateTimeOffset
列)

System.ArgumentException:AtTimeZone 的时间戳参数具有未知的存储类型 NULL(参数“时间戳”)

at Npgsql.EntityFrameworkCore.PostgreSQL.Query.NpgsqlSqlExpressionFactory.AtTimeZone(SqlExpression timestamp, SqlExpression timeZone, Type type, RelationalTypeMapping typeMapping)  
at Npgsql.EntityFrameworkCore.PostgreSQL.Query.NpgsqlSqlExpressionFactory.AtUtc(SqlExpression timestamp, RelationalTypeMapping typeMapping)  
at Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal.NpgsqlDateTimeMemberTranslator.TranslateDateTimeOffset(SqlExpression instance, MemberInfo member)  
at Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal.NpgsqlDateTimeMemberTranslator.Translate(SqlExpression instance, MemberInfo member, Type returnType, IDiagnosticsLogger`1 logger)  
at Microsoft.EntityFrameworkCore.Query.RelationalMemberTranslatorProvider.<>c__DisplayClass6_0.<Translate>b__0(IMemberTranslator t)  
at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext() 

我还尝试将

NodaTime
添加到上下文并使用可翻译函数。

builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(
    "<connection string>",
    o => o.UseNodaTime())); 

但这也不起作用。

c# postgresql linq entity-framework-core timezone
1个回答
0
投票

根据日期和时间函数的文档以及我的测试,不需要额外的函数 -

TimeZoneInfo.ConvertTimeBySystemTimeZoneId
按预期工作。这是一个例子:

var activityPerDay = await transactionQuery
    .GroupBy(t => TimeZoneInfo.ConvertTimeBySystemTimeZoneId(t.CreatedAt, timeZoneId).Date)
    .Select(dayGroup => new ItemMetrics
    {
        DateTime = dayGroup.Key,
        Volume = dayGroup.Sum(t => t.Amount ?? 0)
    })
    .ToListAsync(cancellationToken);
© www.soinside.com 2019 - 2024. All rights reserved.