From 5c8c76ea87d2d8bc26ec7992c8a031290572db4d Mon Sep 17 00:00:00 2001 From: Jiri Cincura Date: Tue, 19 Aug 2025 17:28:57 +0200 Subject: [PATCH] Fix type mapping for GETDATE and GETUTCDATE in SqlServer. --- .../SqlServerDateTimeMemberTranslator.cs | 26 +++++++++++--- ...DateTimeOffsetTranslationsSqlServerTest.cs | 30 ++++++++++++++++ .../DateTimeTranslationsSqlServerTest.cs | 34 +++++++++++++++++-- 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs index 96cccd74180..99ee44946a6 100644 --- a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs @@ -69,12 +69,24 @@ public class SqlServerDateTimeMemberTranslator( returnType), nameof(DateTime.Now) + when declaringType == typeof(DateTime) => sqlExpressionFactory.Function( - declaringType == typeof(DateTime) ? "GETDATE" : "SYSDATETIMEOFFSET", + "GETDATE", arguments: [], nullable: false, argumentsPropagateNullability: [], - returnType), + typeof(DateTime), + typeMappingSource.FindMapping("datetime")), + + nameof(DateTimeOffset.Now) + when declaringType == typeof(DateTimeOffset) + => sqlExpressionFactory.Function( + "SYSDATETIMEOFFSET", + arguments: [], + nullable: false, + argumentsPropagateNullability: [], + typeof(DateTimeOffset), + typeMappingSource.FindMapping("datetimeoffset")), nameof(DateTime.UtcNow) when declaringType == typeof(DateTime) @@ -83,9 +95,10 @@ public class SqlServerDateTimeMemberTranslator( arguments: [], nullable: false, argumentsPropagateNullability: [], - returnType), + typeof(DateTime), + typeMappingSource.FindMapping("datetime")), - nameof(DateTime.UtcNow) + nameof(DateTimeOffset.UtcNow) when declaringType == typeof(DateTimeOffset) => sqlExpressionFactory.Convert( sqlExpressionFactory.Function( @@ -93,7 +106,10 @@ public class SqlServerDateTimeMemberTranslator( arguments: [], nullable: false, argumentsPropagateNullability: [], - returnType), returnType), + typeof(DateTime), + typeMappingSource.FindMapping("datetime2")), + typeof(DateTimeOffset), + typeMappingSource.FindMapping("datetimeoffset")), nameof(DateTime.Today) => sqlExpressionFactory.Function( diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Translations/Temporal/DateTimeOffsetTranslationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Translations/Temporal/DateTimeOffsetTranslationsSqlServerTest.cs index 844781bbffe..17cbcfb86c8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Translations/Temporal/DateTimeOffsetTranslationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Translations/Temporal/DateTimeOffsetTranslationsSqlServerTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel; + namespace Microsoft.EntityFrameworkCore.Query.Translations.Temporal; public class DateTimeOffsetTranslationsSqlServerTest : DateTimeOffsetTranslationsTestBase @@ -298,6 +300,34 @@ FROM [BasicTypesEntities] AS [b] """); } + [ConditionalFact] + public virtual async Task Now_has_proper_type_mapping_for_constant_comparison() + { + await AssertQuery( + ss => ss.Set().Where(x => DateTimeOffset.Now > new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero))); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE SYSDATETIMEOFFSET() > '2025-01-01T00:00:00.0000000+00:00' +"""); + } + + [ConditionalFact] + public virtual async Task UtcNow_has_proper_type_mapping_for_constant_comparison() + { + await AssertQuery( + ss => ss.Set().Where(x => DateTimeOffset.UtcNow > new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero))); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE CAST(SYSUTCDATETIME() AS datetimeoffset) > '2025-01-01T00:00:00.0000000+00:00' +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Translations/Temporal/DateTimeTranslationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Translations/Temporal/DateTimeTranslationsSqlServerTest.cs index 78ba68656a4..d8d1a3e4433 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Translations/Temporal/DateTimeTranslationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Translations/Temporal/DateTimeTranslationsSqlServerTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel; + namespace Microsoft.EntityFrameworkCore.Query.Translations.Temporal; public class DateTimeTranslationsSqlServerTest : DateTimeTranslationsTestBase @@ -18,7 +20,7 @@ public override async Task Now() AssertSql( """ -@myDatetime='2015-04-10T00:00:00.0000000' +@myDatetime='2015-04-10T00:00:00.0000000' (DbType = DateTime) SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] FROM [BasicTypesEntities] AS [b] @@ -32,7 +34,7 @@ public override async Task UtcNow() AssertSql( """ -@myDatetime='2015-04-10T00:00:00.0000000' +@myDatetime='2015-04-10T00:00:00.0000000' (DbType = DateTime) SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] FROM [BasicTypesEntities] AS [b] @@ -241,6 +243,34 @@ FROM [BasicTypesEntities] AS [b] """); } + [ConditionalFact] + public virtual async Task Now_has_proper_type_mapping_for_constant_comparison() + { + await AssertQuery( + ss => ss.Set().Where(x => DateTime.Now > new DateTime(2025, 1, 1))); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE GETDATE() > '2025-01-01T00:00:00.000' +"""); + } + + [ConditionalFact] + public virtual async Task UtcNow_has_proper_type_mapping_for_constant_comparison() + { + await AssertQuery( + ss => ss.Set().Where(x => DateTime.UtcNow > new DateTime(2025, 1, 1))); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE GETUTCDATE() > '2025-01-01T00:00:00.000' +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType());