EF Core 2.2 如何创建含AT TIME ZONE片段的SqlExpression
EF Core 自定义函数映射SQL问题解决
核心问题拆解
你需要实现的目标是将C#方法映射为如下SQL Server逻辑:
CONVERT(datetime, SWITCHOFFSET(GETUTCDATE(), DATEPART(TZOFFSET, GETUTCDATE() AT TIME ZONE 'Greenwich Standard Time')))
原有代码存在两个核心阻塞点:
- 错误实例化基类
Expression创建时区常量 - 不知道如何组合
AT TIME ZONE这类SQL中缀语法对应的表达式节点
具体实现方案
1. 时区常量表达式创建
'Greenwich Standard Time'是SQL层面的字符串常量,不要直接实例化抽象基类Expression,使用SqlConstantExpression包装C#常量值即可,类型映射参数传null,EF Core会在翻译阶段自动适配SQL Server的字符串类型:
var timeZoneConstant = new SqlConstantExpression( Expression.Constant("Greenwich Standard Time"), typeMapping: null );
2. AT TIME ZONE 表达式组合
AT TIME ZONE是SQL Server的中缀运算语法,不是普通函数,不要零散拼接SqlFragmentExpression作为函数参数:
- 如果你使用EF Core 6.0及以上版本,直接使用公开API
AtTimeZoneExpression构造即可,这是官方专门为该语法提供的表达式节点类型:var getUtcDate = new SqlFunctionExpression("GETUTCDATE", typeof(DateTime), typeMapping: null); // 构造 GETUTCDATE() AT TIME ZONE 'Greenwich Standard Time' var atTimeZoneExpr = new AtTimeZoneExpression( operand: getUtcDate, timeZone: timeZoneConstant, type: typeof(DateTimeOffset), typeMapping: null ); - 如果你使用EF Core 3.1/5.0等低版本,没有公开的
AtTimeZoneExpression,可以通过反射创建内部同名类型,或者直接复用SqlFunctionExpression的自定义翻译逻辑生成对应SQL片段,避免硬编码字符串导致的翻译异常。
完整可运行配置代码
modelBuilder.HasDbFunction(typeof(DbContext).GetMethod(nameof(DbContext.GetDateTime))!) .HasTranslation(expressions => { // 构造GETUTCDATE()节点 var getUtcDate = new SqlFunctionExpression( functionName: "GETUTCDATE", returnType: typeof(DateTime), typeMapping: null ); // 构造时区常量节点 var timeZoneConstant = new SqlConstantExpression( Expression.Constant("Greenwich Standard Time"), typeMapping: null ); // 构造AT TIME ZONE运算节点 var atTimeZoneExpr = new AtTimeZoneExpression( operand: getUtcDate, timeZone: timeZoneConstant, type: typeof(DateTimeOffset), typeMapping: null ); // 构造DATEPART(TZOFFSET, ...)节点 var tzOffsetKeyword = new SqlFragmentExpression("TZOFFSET"); var datePartExpr = new SqlFunctionExpression( functionName: "DATEPART", returnType: typeof(int), arguments: new SqlExpression[] { tzOffsetKeyword, atTimeZoneExpr }, typeMapping: null ); // 构造SWITCHOFFSET节点 var switchOffsetExpr = new SqlFunctionExpression( functionName: "SWITCHOFFSET", returnType: typeof(DateTimeOffset), arguments: new SqlExpression[] { getUtcDate, datePartExpr }, typeMapping: null ); // 构造最终CONVERT节点 var datetimeTypeKeyword = new SqlFragmentExpression("DATETIME"); var finalExpr = new SqlFunctionExpression( functionName: "CONVERT", returnType: typeof(DateTime), arguments: new SqlExpression[] { datetimeTypeKeyword, switchOffsetExpr }, typeMapping: null ); // 注意必须返回最终表达式,原代码遗漏了return逻辑 return finalExpr; });
注意事项
- 所有表达式构造时传入的
typeMapping参数统一传null即可,EF Core会在后续翻译管线中自动匹配SQL Server对应的类型映射,不需要手动指定 - 如果使用低版本EF Core无法访问
AtTimeZoneExpression,建议升级到EF Core 6+长期支持版本,避免依赖内部API导致版本升级时出现断裂性变更 - 构造
SqlFunctionExpression时注意传入的返回类型必须和SQL函数实际返回的类型匹配,否则会出现运行时类型转换异常
内容的提问来源于stack exchange,提问作者Weanich Sanchol
相关产品推荐
相关产品推荐

