【问题标题】:EF Core, group by month and year with utc datesEF Core,按月和年分组,带 UTC 日期
【发布时间】:2020-09-21 13:04:20
【问题描述】:

我将所有日期存储为 UTC,我需要按月和年对实体进行分组,所以我正在这样做:

_dbContext.Tickets.Where(x => x.Date >= from && x.Date <= to).GroupBy(x=> new {
                Year = x.Date.Year,
                Month = x.Date.Month
            }).Select(x=> new {x.Key, Count = x.Count()})

由于日期是 UTC,我在 2020 年 1 月 9 日上午 00:30 创建了一张票,但由于它存储为 UTC(我是 +2),它将被存储为 2020-08-31 22: 25.现在,如果我按月分组,我将把这个实体分组在错误的月份。有没有什么简单的方法可以不在内存上做到这一点?

【问题讨论】:

  • 先判断哪里出了问题。您想按 UTC 还是本地时间分组?您可以使用:x.Date.ToLocalTime >= from && x.Date.ToLocalTime
  • @jdweng 我认为 ToLocalTime 不会是 Linq to Entities 的 regonized
  • 过滤器可以忽略,但由于从 e 到都是 UTC,与 db 中的日期相同,过滤器很好,它不需要 .ToLocalTime()。如果我可以在“Month = x.Date.ToLocalTime().Month”之类的分组部分中设置 ToLocalTime,那会很好,但我不能,因为它将在本地进行评估。@jdweng​​span>
  • Sql 服务器?所以要生成sqldate AT TIME ZONE @name?可能有一种方法可以定义自定义数据库函数....
  • 或者定义[DbFunction] public static DateTimeOffset SWITCHOFFSET (DateTimeOffset value, string timezone)? (docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0/…, docs.microsoft.com/en-us/sql/t-sql/functions/…)

标签: c# asp.net-core entity-framework-core


【解决方案1】:

有几种方法可以引入映射到 sql 函数或原始 sql 片段的 c# 函数。

对于直接映射到同名sql函数的函数,只需要定义方法即可;

public static ReturnType CustomMethod(ArgType arg) => throw new NotImplementedException();

protected override void OnModelCreating(ModelBuilder builder){
    builder.HasDbFunction(typeof(Context).GetMethod(nameof(CustomMethod)));
}

或者在该方法上使用[DbFunction] 属性。

要生成像“@date AT TIME ZONE @name”这样的 sql 片段,你不能只使用 SqlFunctionExpression 的实例,还有一些工作要做。

public static DateTimeOffset ToTimeZone(this DateTimeOffset value, string name) => throw new NotImplementedException();

public class SqlFragmentListExpression : SqlExpression
{
    public SqlFragmentListExpression(Type type, RelationalTypeMapping typeMapping, params SqlExpression[] fragments) : base(type, typeMapping)
    {
        Fragments = fragments;
    }

    public SqlExpression[] Fragments { get; }

    public override void Print(ExpressionPrinter expressionPrinter)
    {
        foreach (var f in Fragments)
            f.Print(expressionPrinter);
    }

    protected override Expression VisitChildren(ExpressionVisitor visitor)
    {
        var frags = new SqlExpression[Fragments.Length];
        var changed = false;
        for(var i = 0; i < Fragments.Length; i++)
        {
            frags[i] = (SqlExpression)visitor.Visit(Fragments[i]);
            changed |= frags[i] != Fragments[i];
        }
        return changed ? new SqlFragmentListExpression(Type, TypeMapping, frags) : this;
    }
}

// OnModelCreating
builder                
    .HasDbFunction(typeof(Extensions).GetMethod(nameof(Extensions.ToTimeZone)))
    .HasTranslation(args => {
        var dto = args.ElementAt(0);
        return new SqlFragmentListExpression(dto.Type, dto.TypeMapping,
            dto,
            new SqlFragmentExpression(" AT TIME ZONE "),
            args.ElementAt(1));
    });

然后您可以在类似的查询中使用该方法;

_dbContext.Tickets
    .Where(x => x.Date >= from && x.Date <= to)
    .Select(x => new {
        Date = x.Date.ToTimeZone("Central European Standard Time")
    })
    .GroupBy(x => new {
        Year = x.Date.Year,
        Month = x.Date.Month
    },
    (x, e) => new {
        x.Year,
        x.Month,
        Count = e.Count()
    })

应该翻译成sql;

SELECT DATEPART(year, [t].[Date] AT TIME ZONE N'Central European Standard Time') AS [Year],
    DATEPART(month, [t].[Date] AT TIME ZONE N'Central European Standard Time') AS [Month], 
    COUNT(*) AS [Count]
FROM [Tickets] AS [t]
GROUP BY DATEPART(year, [t].[Date] AT TIME ZONE N'Central European Standard Time'),
    DATEPART(month, [t].[Date] AT TIME ZONE N'Central European Standard Time')

【讨论】:

  • 我将在接下来的几天里尝试这个,我会回来提供反馈。感谢您的解释,似乎是一个好方法。!
  • 我尝试了这个解决方案,但它有一个问题,如果您使用 AT TIME ZONE 转换,它只会在末尾添加小时数,但如果您尝试选择月份,它仍然会选择错误月。这可以使用 2020-08-01 00:30:00.0000000 作为日期进行测试,该日期应翻译为 2020-07-31 22:30:00.0000000 但它在 TIME ZONE 返回 2020-08-01 00:30:00.0000000 +02 :00。我不得不修改 ToTimeZone 方法以使用 Datetime 而不是 DateTimeOffset 因为那是我现在使用的类型
  • 此代码在 ef core 5 中不起作用
猜你喜欢
  • 1970-01-01
  • 2012-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-15
  • 2018-01-07
  • 2014-04-27
  • 1970-01-01
相关资源
最近更新 更多