diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs
index ee51c45cce6..91f7dd0009f 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs
@@ -8,6 +8,7 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
#nullable enable
@@ -41,6 +42,7 @@ public class SqlServerDateTimeMethodTranslator : IMethodCallTranslator
};
private readonly ISqlExpressionFactory _sqlExpressionFactory;
+ private readonly IRelationalTypeMappingSource _typeMappingSource;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -48,9 +50,12 @@ public class SqlServerDateTimeMethodTranslator : IMethodCallTranslator
/// 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 SqlServerDateTimeMethodTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
+ public SqlServerDateTimeMethodTranslator(
+ [NotNull] ISqlExpressionFactory sqlExpressionFactory,
+ [NotNull] IRelationalTypeMappingSource typeMappingSource)
{
_sqlExpressionFactory = sqlExpressionFactory;
+ _typeMappingSource = typeMappingSource;
}
///
@@ -74,24 +79,32 @@ public SqlServerDateTimeMethodTranslator([NotNull] ISqlExpressionFactory sqlExpr
{
// DateAdd does not accept number argument outside of int range
// AddYears/AddMonths take int argument so no need to check for range
- return datePart != "year"
+ if (datePart != "year"
&& datePart != "month"
&& arguments[0] is SqlConstantExpression sqlConstant
&& ((double)sqlConstant.Value >= int.MaxValue
- || (double)sqlConstant.Value <= int.MinValue)
- ? null
- : _sqlExpressionFactory.Function(
- "DATEADD",
- new[]
- {
- _sqlExpressionFactory.Fragment(datePart),
- _sqlExpressionFactory.Convert(arguments[0], typeof(int)),
- instance
- },
- nullable: true,
- argumentsPropagateNullability: new[] { false, true, true },
- instance.Type,
- instance.TypeMapping);
+ || (double)sqlConstant.Value <= int.MinValue))
+ {
+ return null;
+ }
+
+ if (instance is SqlConstantExpression instanceConstant)
+ {
+ instance = instanceConstant.ApplyTypeMapping(_typeMappingSource.FindMapping(typeof(DateTime), "datetime"));
+ }
+
+ return _sqlExpressionFactory.Function(
+ "DATEADD",
+ new[]
+ {
+ _sqlExpressionFactory.Fragment(datePart),
+ _sqlExpressionFactory.Convert(arguments[0], typeof(int)),
+ instance
+ },
+ nullable: true,
+ argumentsPropagateNullability: new[] { false, true, true },
+ instance.Type,
+ instance.TypeMapping);
}
return null;
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerMethodCallTranslatorProvider.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerMethodCallTranslatorProvider.cs
index f3375133dbc..0b2c194c150 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerMethodCallTranslatorProvider.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerMethodCallTranslatorProvider.cs
@@ -34,7 +34,7 @@ public SqlServerMethodCallTranslatorProvider([NotNull] RelationalMethodCallTrans
new SqlServerConvertTranslator(sqlExpressionFactory),
new SqlServerDataLengthFunctionTranslator(sqlExpressionFactory),
new SqlServerDateDiffFunctionsTranslator(sqlExpressionFactory),
- new SqlServerDateTimeMethodTranslator(sqlExpressionFactory),
+ new SqlServerDateTimeMethodTranslator(sqlExpressionFactory, typeMappingSource),
new SqlServerFromPartsFunctionTranslator(sqlExpressionFactory, typeMappingSource),
new SqlServerFullTextSearchFunctionsTranslator(sqlExpressionFactory),
new SqlServerIsDateFunctionTranslator(sqlExpressionFactory),
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
index df795b7a239..d029963733a 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
@@ -2580,6 +2580,17 @@ FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (c[""OrderDate""] != null))");
}
+ public override async Task Add_minutes_on_constant_value(bool async)
+ {
+ await base.Add_minutes_on_constant_value(async);
+
+ AssertSql(
+ @"SELECT VALUE {""c"" : (c[""OrderID""] % 25)}
+FROM root c
+WHERE ((c[""Discriminator""] = ""Order"") AND (c[""OrderID""] < 10500))
+ORDER BY c[""OrderID""]");
+ }
+
[ConditionalTheory(Skip = "Issue #17246")]
public override async Task Select_expression_references_are_updated_correctly_with_subquery(bool async)
{
diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs
index b1721ee75ea..3863f77a95e 100644
--- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs
@@ -4194,6 +4194,19 @@ public virtual Task Select_expression_date_add_milliseconds_large_number_divided
e => e.OrderDate);
}
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Add_minutes_on_constant_value(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(c => c.OrderID < 10500)
+ .OrderBy(o => o.OrderID)
+ .Select(o => new { Test = new DateTime(1900, 1, 1).AddMinutes(o.OrderID % 25) }),
+ assertOrder: true,
+ elementAsserter: (e, a) => AssertEqual(e.Test, a.Test));
+ }
+
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Select_expression_references_are_updated_correctly_with_subquery(bool async)
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs
index ee921bd1f2a..bb8ab933587 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs
@@ -3229,6 +3229,17 @@ FROM [Orders] AS [o]
WHERE [o].[OrderDate] IS NOT NULL");
}
+ public override async Task Add_minutes_on_constant_value(bool async)
+ {
+ await base.Add_minutes_on_constant_value(async);
+
+ AssertSql(
+ @"SELECT DATEADD(minute, CAST(CAST(([o].[OrderID] % 25) AS float) AS int), '1900-01-01T00:00:00.000') AS [Test]
+FROM [Orders] AS [o]
+WHERE [o].[OrderID] < 10500
+ORDER BY [o].[OrderID]");
+ }
+
public override async Task Select_expression_references_are_updated_correctly_with_subquery(bool async)
{
await base.Select_expression_references_are_updated_correctly_with_subquery(async);
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs
index 68caf7b37bd..3aa50e52851 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs
@@ -172,6 +172,17 @@ SELECT rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', ""o"".""OrderDate"", COALESCE(C
WHERE ""o"".""OrderDate"" IS NOT NULL");
}
+ public override async Task Add_minutes_on_constant_value(bool async)
+ {
+ await base.Add_minutes_on_constant_value(async);
+
+ AssertSql(
+ @"SELECT rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', '1900-01-01 00:00:00', CAST(CAST((""o"".""OrderID"" % 25) AS REAL) AS TEXT) || ' minutes'), '0'), '.') AS ""Test""
+FROM ""Orders"" AS ""o""
+WHERE ""o"".""OrderID"" < 10500
+ORDER BY ""o"".""OrderID""");
+ }
+
public override async Task Select_distinct_long_count(bool async)
{
await base.Select_distinct_long_count(async);