diff --git a/All.sln.DotSettings b/All.sln.DotSettings
index 9aeadf36333..4e7bf4fbf52 100644
--- a/All.sln.DotSettings
+++ b/All.sln.DotSettings
@@ -285,6 +285,7 @@ The .NET Foundation licenses this file to you under the MIT license.
True
True
True
+ True
True
True
True
diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
index 1d8e6ae2bcc..626b2e8e15e 100644
--- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs
+++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
@@ -895,6 +895,42 @@ protected override Expression VisitIn(InExpression inExpression)
return inExpression;
}
+ ///
+ protected override Expression VisitAtTimeZone(AtTimeZoneExpression atTimeZoneExpression)
+ {
+ var requiresBrackets = RequiresParentheses(atTimeZoneExpression, atTimeZoneExpression.Operand);
+
+ if (requiresBrackets)
+ {
+ _relationalCommandBuilder.Append("(");
+ }
+
+ Visit(atTimeZoneExpression.Operand);
+
+ if (requiresBrackets)
+ {
+ _relationalCommandBuilder.Append(")");
+ }
+
+ _relationalCommandBuilder.Append(" AT TIME ZONE ");
+
+ requiresBrackets = RequiresParentheses(atTimeZoneExpression, atTimeZoneExpression.TimeZone);
+
+ if (requiresBrackets)
+ {
+ _relationalCommandBuilder.Append("(");
+ }
+
+ Visit(atTimeZoneExpression.TimeZone);
+
+ if (requiresBrackets)
+ {
+ _relationalCommandBuilder.Append(")");
+ }
+
+ return atTimeZoneExpression;
+ }
+
///
/// Gets a SQL operator for a SQL binary operation.
///
@@ -914,7 +950,7 @@ protected virtual bool RequiresParentheses(SqlExpression outerExpression, SqlExp
{
switch (innerExpression)
{
- case LikeExpression:
+ case AtTimeZoneExpression or LikeExpression:
return true;
case SqlUnaryExpression sqlUnaryExpression:
diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs
index 1f0ff84c295..eeddeae5ab0 100644
--- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs
+++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs
@@ -54,6 +54,7 @@ public SqlExpressionFactory(SqlExpressionFactoryDependencies dependencies)
return sqlExpression switch
{
+ AtTimeZoneExpression e => ApplyTypeMappingOnAtTimeZone(e, typeMapping),
CaseExpression e => ApplyTypeMappingOnCase(e, typeMapping),
CollateExpression e => ApplyTypeMappingOnCollate(e, typeMapping),
DistinctExpression e => ApplyTypeMappingOnDistinct(e, typeMapping),
@@ -69,6 +70,9 @@ public SqlExpressionFactory(SqlExpressionFactoryDependencies dependencies)
};
}
+ private SqlExpression ApplyTypeMappingOnAtTimeZone(AtTimeZoneExpression atTimeZoneExpression, RelationalTypeMapping? typeMapping)
+ => new AtTimeZoneExpression(atTimeZoneExpression.Operand, atTimeZoneExpression.TimeZone, atTimeZoneExpression.Type, typeMapping);
+
private SqlExpression ApplyTypeMappingOnLike(LikeExpression likeExpression)
{
var inferredTypeMapping = (likeExpression.EscapeChar == null
diff --git a/src/EFCore.Relational/Query/SqlExpressionVisitor.cs b/src/EFCore.Relational/Query/SqlExpressionVisitor.cs
index f5d9da82cc5..c4872f20a41 100644
--- a/src/EFCore.Relational/Query/SqlExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/SqlExpressionVisitor.cs
@@ -24,6 +24,9 @@ protected override Expression VisitExtension(Expression extensionExpression)
case ShapedQueryExpression shapedQueryExpression:
return shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression));
+ case AtTimeZoneExpression atTimeZoneExpression:
+ return VisitAtTimeZone(atTimeZoneExpression);
+
case CaseExpression caseExpression:
return VisitCase(caseExpression);
@@ -115,6 +118,14 @@ protected override Expression VisitExtension(Expression extensionExpression)
return base.VisitExtension(extensionExpression);
}
+
+ ///
+ /// Visits the children of the sql "at time zone" expression.
+ ///
+ /// The expression to visit.
+ /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
+ protected abstract Expression VisitAtTimeZone(AtTimeZoneExpression atTimeZoneExpression);
+
///
/// Visits the children of the case expression.
///
@@ -277,7 +288,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
protected abstract Expression VisitSqlConstant(SqlConstantExpression sqlConstantExpression);
///
- /// Visits the children of the sql fragent expression.
+ /// Visits the children of the sql fragment expression.
///
/// The expression to visit.
/// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
diff --git a/src/EFCore.Relational/Query/SqlExpressions/AtTimeZoneExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/AtTimeZoneExpression.cs
new file mode 100644
index 00000000000..f4b75295467
--- /dev/null
+++ b/src/EFCore.Relational/Query/SqlExpressions/AtTimeZoneExpression.cs
@@ -0,0 +1,91 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+
+///
+///
+/// An expression that represents an AT TIME ZONE operation in a SQL tree.
+///
+///
+/// This type is typically used by database providers (and other extensions). It is generally
+/// not used in application code.
+///
+///
+public class AtTimeZoneExpression : SqlExpression
+{
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The operand on which to perform the time zone conversion.
+ /// The time zone to convert to.
+ /// The of the expression.
+ /// The associated with the expression.
+ public AtTimeZoneExpression(
+ SqlExpression operand,
+ SqlExpression timeZone,
+ Type type,
+ RelationalTypeMapping? typeMapping)
+ : base(type, typeMapping)
+ {
+ Operand = operand;
+ TimeZone = timeZone;
+ }
+
+ ///
+ /// The input operand on which to apply the time zone.
+ ///
+ public virtual SqlExpression Operand { get; }
+
+ ///
+ /// The time zone to be applied.
+ ///
+ public virtual SqlExpression TimeZone { get; }
+
+ ///
+ protected override Expression VisitChildren(ExpressionVisitor visitor)
+ {
+ var operand = (SqlExpression)visitor.Visit(Operand);
+ var timeZone = (SqlExpression)visitor.Visit(TimeZone);
+
+ return Update(operand, timeZone);
+ }
+
+ ///
+ /// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will
+ /// return this expression.
+ ///
+ /// The property of the result.
+ /// The property of the result.
+ /// This expression if no children changed, or an expression with the updated children.
+ public virtual AtTimeZoneExpression Update(SqlExpression operand, SqlExpression timeZone)
+ => operand != Operand || timeZone != TimeZone
+ ? new AtTimeZoneExpression(operand, timeZone, Type, TypeMapping)
+ : this;
+
+ ///
+ protected override void Print(ExpressionPrinter expressionPrinter)
+ {
+ expressionPrinter.Visit(Operand);
+
+ expressionPrinter.Append(" AT TIME ZONE ");
+
+ expressionPrinter.Visit(TimeZone);
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ => obj != null
+ && (ReferenceEquals(this, obj)
+ || obj is AtTimeZoneExpression atTimeZoneExpression
+ && Equals(atTimeZoneExpression));
+
+ private bool Equals(AtTimeZoneExpression atTimeZoneExpression)
+ => base.Equals(atTimeZoneExpression)
+ && Operand.Equals(atTimeZoneExpression.Operand)
+ && TimeZone.Equals(atTimeZoneExpression.TimeZone);
+
+ ///
+ public override int GetHashCode()
+ => HashCode.Combine(base.GetHashCode(), Operand, TimeZone);
+}
diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
index 9d03eaf7d2b..ae575a52c66 100644
--- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
+++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
@@ -357,6 +357,8 @@ protected virtual SelectExpression Visit(SelectExpression selectExpression)
var nullValueColumnsCount = _nullValueColumns.Count;
var result = sqlExpression switch
{
+ AtTimeZoneExpression sqlAtTimeZoneExpression
+ => VisitAtTimeZone(sqlAtTimeZoneExpression, allowOptimizedExpansion, out nullable),
CaseExpression caseExpression
=> VisitCase(caseExpression, allowOptimizedExpansion, out nullable),
CollateExpression collateExpression
@@ -413,6 +415,25 @@ protected virtual SqlExpression VisitCustomSqlExpression(
=> throw new InvalidOperationException(
RelationalStrings.UnhandledExpressionInVisitor(sqlExpression, sqlExpression.GetType(), nameof(SqlNullabilityProcessor)));
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected virtual SqlExpression VisitAtTimeZone(
+ AtTimeZoneExpression atTimeZoneExpression,
+ bool allowOptimizedExpansion,
+ out bool nullable)
+ {
+ var operand = Visit(atTimeZoneExpression.Operand, out var operandNullable);
+ var timeZone = Visit(atTimeZoneExpression.TimeZone, out var timeZoneNullable);
+
+ nullable = operandNullable || timeZoneNullable;
+
+ return atTimeZoneExpression.Update(operand, timeZone);
+ }
+
///
/// Visits a and computes its nullability.
///
diff --git a/src/EFCore.SqlServer/Extensions/SqlServerDbFunctionsExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerDbFunctionsExtensions.cs
index d41be8cd35e..f4de9db02ab 100644
--- a/src/EFCore.SqlServer/Extensions/SqlServerDbFunctionsExtensions.cs
+++ b/src/EFCore.SqlServer/Extensions/SqlServerDbFunctionsExtensions.cs
@@ -1438,4 +1438,50 @@ public static bool IsNumeric(
this DbFunctions _,
string expression)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(IsNumeric)));
+
+ ///
+ /// Converts to the corresponding datetimeoffset in the target .
+ /// Corresponds to the SQL Server's AT TIME ZONE construct.
+ ///
+ ///
+ ///
+ /// Note that the of is not taken into account when performing the
+ /// conversion; the offset for the provided time zone is simply applied as-is.
+ ///
+ ///
+ /// See Database functions, and
+ /// Accessing SQL Server and SQL Azure databases with EF Core
+ /// for more information and examples.
+ ///
+ ///
+ /// The instance.
+ /// The value to convert to datetimeoffset.
+ /// A valid SQL Server time zone ID.
+ /// The datetimeoffset resulting from the conversion.
+ /// SQL Server documentation for AT TIME ZONE.
+ public static DateTimeOffset AtTimeZone(
+ this DbFunctions _,
+ DateTime dateTime,
+ string timeZone)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(AtTimeZone)));
+
+ ///
+ /// Converts to the time zone specified by .
+ /// Corresponds to the SQL Server's AT TIME ZONE construct.
+ ///
+ ///
+ /// See Database functions, and
+ /// Accessing SQL Server and SQL Azure databases with EF Core
+ /// for more information and examples.
+ ///
+ /// The instance.
+ /// The value on which to perform the time zone conversion.
+ /// A valid SQL Server time zone ID.
+ /// The datetimeoffset resulting from the conversion.
+ /// SQL Server documentation for AT TIME ZONE.
+ public static DateTimeOffset AtTimeZone(
+ this DbFunctions _,
+ DateTimeOffset dateTimeOffset,
+ string timeZone)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(AtTimeZone)));
}
diff --git a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs
index 37129cd8f8e..6d79e7872fe 100644
--- a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs
+++ b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs
@@ -123,6 +123,7 @@ public static IServiceCollection AddEntityFrameworkSqlServer(this IServiceCollec
.TryAdd()
.TryAdd()
.TryAdd()
+ .TryAdd()
.TryAdd()
.TryAdd()
.TryAdd()
diff --git a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
index 497ff74aef3..9a5bb6c9c91 100644
--- a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
@@ -306,6 +306,23 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
: selectExpression;
}
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override Expression VisitAtTimeZone(AtTimeZoneExpression atTimeZoneExpression)
+ {
+ var parentSearchCondition = _isSearchCondition;
+ _isSearchCondition = false;
+ var operand = (SqlExpression)Visit(atTimeZoneExpression.Operand);
+ var timeZone = (SqlExpression)Visit(atTimeZoneExpression.TimeZone);
+ _isSearchCondition = parentSearchCondition;
+
+ return atTimeZoneExpression.Update(operand, timeZone);
+ }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs
index f5469dbb8cf..f962d6d84e7 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs
@@ -31,6 +31,14 @@ public class SqlServerDateTimeMethodTranslator : IMethodCallTranslator
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMilliseconds), new[] { typeof(double) })!, "millisecond" }
};
+ private static readonly MethodInfo AtTimeZoneDateTimeOffsetMethodInfo = typeof(SqlServerDbFunctionsExtensions)
+ .GetRuntimeMethod(
+ nameof(SqlServerDbFunctionsExtensions.AtTimeZone), new[] { typeof(DbFunctions), typeof(DateTimeOffset), typeof(string) })!;
+
+ private static readonly MethodInfo AtTimeZoneDateTimeMethodInfo = typeof(SqlServerDbFunctionsExtensions)
+ .GetRuntimeMethod(
+ nameof(SqlServerDbFunctionsExtensions.AtTimeZone), new[] { typeof(DbFunctions), typeof(DateTime), typeof(string) })!;
+
private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly IRelationalTypeMappingSource _typeMappingSource;
@@ -89,6 +97,48 @@ public SqlServerDateTimeMethodTranslator(
instance.TypeMapping);
}
+ if (method == AtTimeZoneDateTimeOffsetMethodInfo || method == AtTimeZoneDateTimeMethodInfo)
+ {
+ var (operand, timeZone) = (arguments[1], arguments[2]);
+
+ RelationalTypeMapping? resultTypeMapping = null;
+
+ // The AT TIME ZONE construct bubbles up the precision of its operand, so when invoked over datetime2(2) it returns a
+ // datetimeoffset(2). So if the operand has a type mapping, bubble it up accordingly, otherwise allow the result type mapping
+ // to be inferred.
+ if (operand.TypeMapping is { } operandTypeMapping)
+ {
+ switch (operandTypeMapping.StoreTypeNameBase)
+ {
+ case "datetimeoffset":
+ resultTypeMapping = operandTypeMapping;
+ break;
+ case "datetime" or "datetime2" or "smalldatetime":
+ resultTypeMapping = _typeMappingSource.FindMapping(
+ typeof(DateTimeOffset), "datetimeoffset", precision: operandTypeMapping.Precision);
+ break;
+ default:
+ Check.DebugAssert(
+ false,
+ $"Unknown operand type mapping '{operandTypeMapping.StoreTypeNameBase}' when translating EF.Functions.AtTimeZone");
+ break;
+ }
+ }
+
+ if (operand is SqlConstantExpression)
+ {
+ // Our constant representation for datetime/datetimeoffset is an untyped string literal, which the AT TIME ZONE expression
+ // does not accept. Type it explicitly.
+ operand = _sqlExpressionFactory.Convert(operand, operand.Type);
+ }
+
+ return new AtTimeZoneExpression(
+ operand,
+ _sqlExpressionFactory.ApplyTypeMapping(timeZone, _typeMappingSource.FindMapping("varchar")),
+ typeof(DateTimeOffset),
+ resultTypeMapping);
+ }
+
return null;
}
}
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlExpressionFactory.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlExpressionFactory.cs
new file mode 100644
index 00000000000..3de428587f3
--- /dev/null
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlExpressionFactory.cs
@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+
+namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public class SqlServerSqlExpressionFactory : SqlExpressionFactory
+{
+ private IRelationalTypeMappingSource _typeMappingSource;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public SqlServerSqlExpressionFactory(SqlExpressionFactoryDependencies dependencies)
+ : base(dependencies)
+ => _typeMappingSource = dependencies.TypeMappingSource;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [return: NotNullIfNotNull("sqlExpression")]
+ public override SqlExpression? ApplyTypeMapping(SqlExpression? sqlExpression, RelationalTypeMapping? typeMapping)
+ {
+#pragma warning disable IDE0046 // Convert to conditional expression
+ if (sqlExpression == null
+#pragma warning restore IDE0046 // Convert to conditional expression
+ || sqlExpression.TypeMapping != null)
+ {
+ return sqlExpression;
+ }
+
+ return sqlExpression is AtTimeZoneExpression atTimeZoneExpression
+ ? ApplyTypeMappingOnAtTimeZone(atTimeZoneExpression, typeMapping)
+ : base.ApplyTypeMapping(sqlExpression, typeMapping);
+ }
+
+ private SqlExpression ApplyTypeMappingOnAtTimeZone(AtTimeZoneExpression atTimeZoneExpression, RelationalTypeMapping? typeMapping)
+ {
+ var operandTypeMapping = typeMapping is null
+ ? null
+ : atTimeZoneExpression.Operand.Type == typeof(DateTimeOffset)
+ ? typeMapping
+ : atTimeZoneExpression.Operand.Type == typeof(DateTime)
+ ? _typeMappingSource.FindMapping(typeof(DateTime), "datetime2", precision: typeMapping.Precision)
+ : null;
+
+ return new AtTimeZoneExpression(
+ operandTypeMapping is null ? atTimeZoneExpression.Operand : ApplyTypeMapping(atTimeZoneExpression.Operand, operandTypeMapping),
+ atTimeZoneExpression.TimeZone,
+ atTimeZoneExpression.Type,
+ typeMapping);
+ }
+}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
index a3ebf9f0952..28fec1e11a5 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
@@ -2534,6 +2534,145 @@ FROM [Missions] AS [m]
WHERE [m].[Timeline] = '1902-01-02T10:00:00.1234567+01:30'");
}
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual async Task Where_AtTimeZone_datetimeoffset_constant(bool async)
+ {
+ using var context = CreateContext();
+
+ var query = context.Set().Where(
+ m => EF.Functions.AtTimeZone(m.Timeline, "UTC") == new DateTimeOffset(2, 3, 1, 13, 0, 0, TimeSpan.Zero));
+
+ var missions = async
+ ? await query.ToListAsync()
+ : query.ToList();
+
+ var mission = Assert.Single(missions);
+ Assert.Equal(2, mission.Id);
+
+ AssertSql(
+ @"SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
+FROM [Missions] AS [m]
+WHERE ([m].[Timeline] AT TIME ZONE 'UTC') = '0002-03-01T13:00:00.0000000+00:00'");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual async Task Where_AtTimeZone_datetimeoffset_parameter(bool async)
+ {
+ using var context = CreateContext();
+
+ var dateTime = new DateTimeOffset(2, 3, 1, 13, 0, 0, TimeSpan.Zero);
+ var timeZone = "UTC";
+ var query = context.Set().Where(m => m.Timeline == EF.Functions.AtTimeZone(dateTime, timeZone));
+
+ var missions = async
+ ? await query.ToListAsync()
+ : query.ToList();
+
+ var mission = Assert.Single(missions);
+ Assert.Equal(2, mission.Id);
+
+ AssertSql(
+ @"@__dateTime_1='0002-03-01T13:00:00.0000000+00:00'
+@__timeZone_2='UTC' (Size = 8000) (DbType = AnsiString)
+
+SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
+FROM [Missions] AS [m]
+WHERE [m].[Timeline] = (@__dateTime_1 AT TIME ZONE @__timeZone_2)");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual async Task Where_AtTimeZone_datetimeoffset_column(bool async)
+ {
+ using var context = CreateContext();
+
+ var query = context.Set()
+ .Where(m => EF.Functions.AtTimeZone(m.Timeline, "UTC") == new DateTimeOffset(2, 3, 1, 13, 0, 0, TimeSpan.Zero));
+
+ var missions = async
+ ? await query.ToListAsync()
+ : query.ToList();
+
+ var mission = Assert.Single(missions);
+ Assert.Equal(2, mission.Id);
+
+ AssertSql(
+ @"SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
+FROM [Missions] AS [m]
+WHERE ([m].[Timeline] AT TIME ZONE 'UTC') = '0002-03-01T13:00:00.0000000+00:00'");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual async Task Where_AtTimeZone_datetime_constant(bool async)
+ {
+ using var context = CreateContext();
+
+ var query = context.Set().Where(m => m.Timeline == EF.Functions.AtTimeZone(new DateTime(10, 5, 3, 12, 0, 0), "UTC"));
+
+ var missions = async
+ ? await query.ToListAsync()
+ : query.ToList();
+
+ var mission = Assert.Single(missions);
+ Assert.Equal(3, mission.Id);
+
+ AssertSql(
+ @"SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
+FROM [Missions] AS [m]
+WHERE [m].[Timeline] = (CAST('0010-05-03T12:00:00.0000000' AS datetime2) AT TIME ZONE 'UTC')");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual async Task Where_AtTimeZone_datetime_parameter(bool async)
+ {
+ using var context = CreateContext();
+
+ var dateTime = new DateTime(10, 5, 3, 12, 0, 0);
+ var timeZone = "UTC";
+ var query = context.Set().Where(m => m.Timeline == EF.Functions.AtTimeZone(dateTime, timeZone));
+
+ var missions = async
+ ? await query.ToListAsync()
+ : query.ToList();
+
+ var mission = Assert.Single(missions);
+ Assert.Equal(3, mission.Id);
+
+ AssertSql(
+ @"@__dateTime_1='0010-05-03T12:00:00.0000000'
+@__timeZone_2='UTC' (Size = 8000) (DbType = AnsiString)
+
+SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
+FROM [Missions] AS [m]
+WHERE [m].[Timeline] = (@__dateTime_1 AT TIME ZONE @__timeZone_2)");
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual async Task Where_AtTimeZone_datetime_column(bool async)
+ {
+ using var context = CreateContext();
+
+ var query = context.Set()
+ .Where(ct => EF.Functions.AtTimeZone(ct.IssueDate, "UTC") == new DateTimeOffset(15, 3, 7, 0, 0, 0, TimeSpan.Zero));
+
+ var missions = async
+ ? await query.ToListAsync()
+ : query.ToList();
+
+ var mission = Assert.Single(missions);
+ Assert.Equal(Guid.Parse("A7BE028A-0CF2-448F-AB55-CE8BC5D8CF69"), mission.Id);
+
+ AssertSql(
+ @"SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note]
+FROM [Tags] AS [t]
+WHERE ([t].[IssueDate] AT TIME ZONE 'UTC') = '0015-03-07T00:00:00.0000000+00:00'");
+ }
+
public override async Task Orderby_added_for_client_side_GroupJoin_composite_dependent_to_principal_LOJ_when_incomplete_key_is_used(
bool async)
{