diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs index 4c07cc883c8..9e75ade3516 100644 --- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs +++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs @@ -1003,6 +1003,37 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall) Call( EnumerableMethods.SequenceEqual.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]), unwrappedSpanArg, unwrappedOtherArg)); + + // .NET 11 added Min/Max Span overloads; rewrite to Enumerable.Min/Max. + case "Min" + when methodCall.Arguments is [var spanArg] + && TryUnwrapSpanImplicitCast(spanArg, out var unwrappedSpanArg): + { + var elementType = methodCall.Method.ReturnType; + var enumerableMin = EnumerableMethods.GetMinWithoutSelector(elementType); + + if (enumerableMin.IsGenericMethodDefinition) + { + enumerableMin = enumerableMin.MakeGenericMethod(elementType); + } + + return Visit(Call(enumerableMin, unwrappedSpanArg)); + } + + case "Max" + when methodCall.Arguments is [var spanArg] + && TryUnwrapSpanImplicitCast(spanArg, out var unwrappedSpanArg): + { + var elementType = methodCall.Method.ReturnType; + var enumerableMax = EnumerableMethods.GetMaxWithoutSelector(elementType); + + if (enumerableMax.IsGenericMethodDefinition) + { + enumerableMax = enumerableMax.MakeGenericMethod(elementType); + } + + return Visit(Call(enumerableMax, unwrappedSpanArg)); + } } static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs index 25bc5e5f408..a607ae58f4a 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs @@ -2076,6 +2076,36 @@ FROM root c """); } +#if NET11_0_OR_GREATER + public override async Task Min_on_MemoryExtensions() + { + await base.Min_on_MemoryExtensions(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (( + SELECT VALUE MIN(a) + FROM a IN (SELECT VALUE [30, c["Int"]])) = 30) +"""); + } + + public override async Task Max_on_MemoryExtensions() + { + await base.Max_on_MemoryExtensions(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (( + SELECT VALUE MAX(a) + FROM a IN (SELECT VALUE [30, c["Int"]])) = 30) +"""); + } +#endif + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs index 00bc11d980d..60745cde1c2 100644 --- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs @@ -955,6 +955,22 @@ public virtual Task Contains_with_MemoryExtensions_with_null_comparer() => AssertQuery( ss => ss.Set().Where(c => MemoryExtensions.Contains(new[] { 10, 999 }, c.Int, comparer: null))); +#if NET11_0_OR_GREATER + // .NET 11 first-class spans caused MemoryExtensions.Min to get resolved instead of Enumerable.Min. + // The following tests that the various overloads are all supported. + [ConditionalFact] + public virtual Task Min_on_MemoryExtensions() + => AssertQuery( + ss => ss.Set().Where(c => MemoryExtensions.Min(new[] { 30, c.Int }) == 30)); + + // .NET 11 first-class spans caused MemoryExtensions.Max to get resolved instead of Enumerable.Max. + // The following tests that the various overloads are all supported. + [ConditionalFact] + public virtual Task Max_on_MemoryExtensions() + => AssertQuery( + ss => ss.Set().Where(c => MemoryExtensions.Max(new[] { 30, c.Int }) == 30)); +#endif + [ConditionalFact] public virtual Task Column_collection_Count_method() => AssertQuery( diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs index 9ba60047cb2..33645bccece 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs @@ -1010,6 +1010,36 @@ WHERE [p].[Int] IN (10, 999) """); } +#if NET11_0_OR_GREATER + public override async Task Min_on_MemoryExtensions() + { + await base.Min_on_MemoryExtensions(); + + AssertSql( + """ +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE ( + SELECT MIN([v].[Value]) + FROM (VALUES (CAST(30 AS int)), ([p].[Int])) AS [v]([Value])) = 30 +"""); + } + + public override async Task Max_on_MemoryExtensions() + { + await base.Max_on_MemoryExtensions(); + + AssertSql( + """ +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE ( + SELECT MAX([v].[Value]) + FROM (VALUES (CAST(30 AS int)), ([p].[Int])) AS [v]([Value])) = 30 +"""); + } +#endif + public override Task Column_collection_Count_method() => AssertCompatibilityLevelTooLow(() => base.Column_collection_Count_method()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs index 102e3bb2e54..aa3919017b7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs @@ -1096,6 +1096,32 @@ WHERE [p].[Int] IN (10, 999) """); } +#if NET11_0_OR_GREATER + public override async Task Min_on_MemoryExtensions() + { + await base.Min_on_MemoryExtensions(); + + AssertSql( + """ +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE LEAST(30, [p].[Int]) = 30 +"""); + } + + public override async Task Max_on_MemoryExtensions() + { + await base.Max_on_MemoryExtensions(); + + AssertSql( + """ +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE GREATEST(30, [p].[Int]) = 30 +"""); + } +#endif + public override async Task Column_collection_Count_method() { await base.Column_collection_Count_method(); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs index 2f652bd5664..a1a162f1b63 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs @@ -1268,6 +1268,32 @@ WHERE [p].[Int] IN (10, 999) """); } +#if NET11_0_OR_GREATER + public override async Task Min_on_MemoryExtensions() + { + await base.Min_on_MemoryExtensions(); + + AssertSql( + """ +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE LEAST(30, [p].[Int]) = 30 +"""); + } + + public override async Task Max_on_MemoryExtensions() + { + await base.Max_on_MemoryExtensions(); + + AssertSql( + """ +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE GREATEST(30, [p].[Int]) = 30 +"""); + } +#endif + public override async Task Column_collection_Count_method() { await base.Column_collection_Count_method(); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs index 68246b08e36..bd85ed66fa0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs @@ -1118,6 +1118,36 @@ WHERE [p].[Int] IN (10, 999) """); } +#if NET11_0_OR_GREATER + public override async Task Min_on_MemoryExtensions() + { + await base.Min_on_MemoryExtensions(); + + AssertSql( + """ +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE ( + SELECT MIN([v].[Value]) + FROM (VALUES (CAST(30 AS int)), ([p].[Int])) AS [v]([Value])) = 30 +"""); + } + + public override async Task Max_on_MemoryExtensions() + { + await base.Max_on_MemoryExtensions(); + + AssertSql( + """ +SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE ( + SELECT MAX([v].[Value]) + FROM (VALUES (CAST(30 AS int)), ([p].[Int])) AS [v]([Value])) = 30 +"""); + } +#endif + public override async Task Column_collection_Count_method() { await base.Column_collection_Count_method(); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs index 844d0975b3e..6fd37f1c338 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs @@ -1048,6 +1048,32 @@ public override async Task Contains_with_MemoryExtensions_with_null_comparer() """); } +#if NET11_0_OR_GREATER + public override async Task Min_on_MemoryExtensions() + { + await base.Min_on_MemoryExtensions(); + + AssertSql( + """ +SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."NullableString", "p"."NullableStrings", "p"."NullableWrappedId", "p"."NullableWrappedIdWithNullableComparer", "p"."String", "p"."Strings", "p"."WrappedId" +FROM "PrimitiveCollectionsEntity" AS "p" +WHERE min(30, "p"."Int") = 30 +"""); + } + + public override async Task Max_on_MemoryExtensions() + { + await base.Max_on_MemoryExtensions(); + + AssertSql( + """ +SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."NullableString", "p"."NullableStrings", "p"."NullableWrappedId", "p"."NullableWrappedIdWithNullableComparer", "p"."String", "p"."Strings", "p"."WrappedId" +FROM "PrimitiveCollectionsEntity" AS "p" +WHERE max(30, "p"."Int") = 30 +"""); + } +#endif + public override async Task Column_collection_Count_method() { await base.Column_collection_Count_method();