c# - EF Core 2.2:将时区转换添加到 select、groupby 和/或 where 上的 datetime2 列(修改/丰富查询上的属性映射)
问题描述
首先,我想要做的与这个问题中解释的非常相似,即使@jeremy-lakeman 的解决方案也适合,但似乎它只适用于 +EF Core 3.0。
我真的很感激使用方法扩展(即 x.AddedDate.ToTimeZone("(timezone id)") 时拦截为日期时间实体属性生成的 SQL 的方法,以便添加时区转换。
我见过 ModelBuilder.HasDbFunction().HasTranslation() 方法,但我不确定那里使用哪种类型的表达式(SqlFunctionExpression、SqlFragmentExpression 等)。将是一种截取生成的默认 SQL 以对其进行格式化的方法吗?
var result = context.DbSet<Ticket>().Select(x => new
{
LocalDate = x.TicketDateUtc.ToTimeZone("Romance Standard Time")
}).First();
打开:
SELECT [x].TicketDateUtc AT TIMEZONE 'Romance Standard Time' FROM Tickets AS [x]
解决方案
所以最后我设法得到了一个基于Jeremy 解决方案的初步解决方案。它可以解决问题,但必须进行很多改进。目前,它仅适用于要转换的预期日期时间是表的列(但可以扩展到常量或参数)。
这是在 OnModelCreating 事件期间实现扩展方法并在上下文的 ModelBuilder 上将它们注册为 DbFunction 的部分:
public static partial class CustomDbFunctions
{
public static DateTime? ToTimeZone(this DateTime? source, string timeZone)
{
if (!source.HasValue) return null;
return DateTimeHelper.UtcDateToLocal(source.Value, timeZone);
}
public static DateTime ToTimeZone(this DateTime source, string timeZone)
{
return ToTimeZone((DateTime?)source, timeZone).Value;
}
public static ModelBuilder AddCustomFunctions(this ModelBuilder builder)
{
builder.HasDbFunction(typeof(CustomDbFunctions).GetMethod(nameof(ToTimeZone), new[] { typeof(DateTime), typeof(string) }))
.HasTranslation(args =>
{
var dateTimeExpression = args.ElementAt(0);
if (dateTimeExpression is ColumnExpression column)
{
return new TimeZoneColumnExpression(column.Name, column.Property, column.Table, args.ElementAt(1));
}
return dateTimeExpression;
});
builder.HasDbFunction(typeof(CustomDbFunctions).GetMethod(nameof(ToTimeZone), new[] { typeof(DateTime?), typeof(string) }))
.HasTranslation(args =>
{
var dateTimeExpression = args.ElementAt(0);
if (dateTimeExpression is ColumnExpression column)
{
return new TimeZoneColumnExpression(column.Name, column.Property, column.Table, args.ElementAt(1));
}
return dateTimeExpression;
});
return builder;
}
}
这是派生自 的自定义表达式Microsoft.EntityFrameworkCore.Query.Expressions.ColumnExpression
。它只拦截 QuerySqlGenerator 以添加一些 sql 片段:
public class TimeZoneColumnExpression : ColumnExpression
{
private readonly Expression timeZoneId;
public TimeZoneColumnExpression(string name, IProperty property, TableExpressionBase tableExpression, Expression timeZoneId) : base(name, property, tableExpression)
{
this.timeZoneId = timeZoneId ?? throw new ArgumentNullException(nameof(timeZoneId));
}
protected override Expression Accept(ExpressionVisitor visitor)
{
if (!(visitor is IQuerySqlGenerator))
return base.Accept(visitor);
visitor.Visit(new SqlFragmentExpression("CONVERT(datetime2, "));
base.Accept(visitor);
visitor.Visit(new SqlFragmentExpression($" AT TIME ZONE 'UTC' AT TIME ZONE "));
visitor.Visit(timeZoneId);
visitor.Visit(new SqlFragmentExpression(")"));
return this;
}
}
利用:
var timeZone = TimeZoneInfo.FindSystemTimeZoneById(TimeZoneConverter.TZConvert.IanaToWindows("Europe/Madrid"));
var groups = await repository.AsQueryable<User>().Where(x => x.Id > 0)
.GroupBy(x => new { x.BeginDateUtc.ToTimeZone(timeZone.Id).Date })
.Select(x =>
new
{
Date = x.Key,
Count = x.Count()
}).ToListAsync();
输出:
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (80ms) [Parameters=[@__timeZone_Id_0='Romance Standard Time' (Size = 4000)], CommandType='Text', CommandTimeout='120']
SELECT CONVERT(date, CONVERT(datetime2, [x].[BeginDateUtc] AT TIME ZONE 'UTC' AT TIME ZONE @__timeZone_Id_0)) AS [Date], COUNT(*) AS [Count]
FROM [dbo].[Users] AS [x]
WHERE [x].[Id] > CAST(0 AS bigint)
GROUP BY CONVERT(date, CONVERT(datetime2, [x].[BeginDateUtc] AT TIME ZONE 'UTC' AT TIME ZONE @__timeZone_Id_0))
推荐阅读
- reactjs - 使用旧版本的 react 创建-react-app
- python - pygame如何将文本添加到矩形以仅更新该文本
- python - Django 发送具有禁止访问权限的电子邮件
- powerbi - DAX Measure 用于计算制造和产品中的不同特征数
- python-3.x - multiply(a, b) 函数的时间复杂度
- matplotlib - 与在 jupyter notebook 中渲染相比,保存后 Matplotlib 图形看起来不同
- c - C 和 Masm 程序
- python - python sdist 中应该包含哪些文件?
- java - 对正则表达式感到困惑?如何阅读正则表达式
- web-services - Web 服务 Eclipse