From 0d6e7016be647126551c0775bd57a4ff9dc074e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Cincura=20=E2=86=B9?= Date: Thu, 6 Nov 2025 08:44:02 +0100 Subject: [PATCH 01/11] Fix 0-byte reads/writes on blobs (#37068) --- src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs b/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs index bbcb2a263ef..7e1088b8cb8 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs @@ -206,8 +206,12 @@ public virtual int Read(Span buffer) count = (int)(Length - position); } - var rc = sqlite3_blob_read(_blob, buffer.Slice(0, count), (int)position); - SqliteException.ThrowExceptionForRC(rc, _connection.Handle); + // Newer sqlite3_blob_read returns error for 0-byte reads. + if (count > 0) + { + var rc = sqlite3_blob_read(_blob, buffer.Slice(0, count), (int)position); + SqliteException.ThrowExceptionForRC(rc, _connection.Handle); + } _position += count; return count; } @@ -280,8 +284,12 @@ public virtual void Write(ReadOnlySpan buffer) throw new NotSupportedException(Resources.ResizeNotSupported); } - var rc = sqlite3_blob_write(_blob, buffer.Slice(0, count), (int)position); - SqliteException.ThrowExceptionForRC(rc, _connection.Handle); + // Newer sqlite3_blob_write returns error for 0-byte writes. + if (count > 0) + { + var rc = sqlite3_blob_write(_blob, buffer.Slice(0, count), (int)position); + SqliteException.ThrowExceptionForRC(rc, _connection.Handle); + } _position += count; } From 6a31ec71a81c0136cee6aa3e727eba2bca5014dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Cincura=20=E2=86=B9?= Date: Thu, 6 Nov 2025 08:44:06 +0100 Subject: [PATCH 02/11] Fix 0-byte reads/writes on blobs (#37067) --- src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs b/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs index bbcb2a263ef..7e1088b8cb8 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs @@ -206,8 +206,12 @@ public virtual int Read(Span buffer) count = (int)(Length - position); } - var rc = sqlite3_blob_read(_blob, buffer.Slice(0, count), (int)position); - SqliteException.ThrowExceptionForRC(rc, _connection.Handle); + // Newer sqlite3_blob_read returns error for 0-byte reads. + if (count > 0) + { + var rc = sqlite3_blob_read(_blob, buffer.Slice(0, count), (int)position); + SqliteException.ThrowExceptionForRC(rc, _connection.Handle); + } _position += count; return count; } @@ -280,8 +284,12 @@ public virtual void Write(ReadOnlySpan buffer) throw new NotSupportedException(Resources.ResizeNotSupported); } - var rc = sqlite3_blob_write(_blob, buffer.Slice(0, count), (int)position); - SqliteException.ThrowExceptionForRC(rc, _connection.Handle); + // Newer sqlite3_blob_write returns error for 0-byte writes. + if (count > 0) + { + var rc = sqlite3_blob_write(_blob, buffer.Slice(0, count), (int)position); + SqliteException.ThrowExceptionForRC(rc, _connection.Handle); + } _position += count; } From cf59185ba3aec4807a5e803354052b69f1c3d1a9 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Thu, 6 Nov 2025 13:55:38 -0800 Subject: [PATCH 03/11] Update BinSkim to 4.3.1 (#37091) --- azure-pipelines.yml | 10 ++++++++++ global.json | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8505fdc64a8..0a45718fc81 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -47,7 +47,9 @@ extends: parameters: featureFlags: autoBaseline: false + usePrefastVersion3: true autoEnableRoslynWithNewRuleset: false + binskimScanAllExtensions: true sdl: sourceAnalysisPool: name: NetCore1ESPool-Svc-Internal @@ -57,6 +59,8 @@ extends: baselineFile: $(Build.SourcesDirectory)\.config\guardian\.gdnbaselines binskim: scanOutputDirectoryOnly: true + analyzeTargetGlob: +:f|**/Microsoft.EntityFrameworkCore*.dll;+:f|**/Microsoft.Data.Sqlite*.dll;+:f|**/ef.exe;+:f|**/dotnet-ef.exe;-:f|**/shims/**/*.exe; + preReleaseVersion: '4.3.1' customBuildTags: - ES365AIMigrationTooling stages: @@ -152,6 +156,9 @@ extends: COMPlus_EnableWriteXorExecute: 0 displayName: Build templateContext: + sdl: + binskim: + prereleaseVersion: ' ' outputs: - output: pipelineArtifact displayName: Upload TestResults @@ -190,6 +197,9 @@ extends: - script: eng/common/cibuild.sh --configuration $(_BuildConfig) --prepareMachine $(_InternalRuntimeDownloadArgs) displayName: Build templateContext: + sdl: + binskim: + prereleaseVersion: ' ' outputs: - output: pipelineArtifact displayName: Upload TestResults diff --git a/global.json b/global.json index 5446e45cba8..1747525a9c6 100644 --- a/global.json +++ b/global.json @@ -1,11 +1,11 @@ { "sdk": { - "version": "8.0.120", + "version": "8.0.121", "allowPrerelease": true, "rollForward": "latestMajor" }, "tools": { - "dotnet": "8.0.120", + "dotnet": "8.0.121", "runtimes": { "dotnet": [ "$(MicrosoftNETCoreBrowserDebugHostTransportVersion)" From 51bd1bd7aa3b272186a9c7f3e5f43ca0b00cbac4 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 19 Nov 2025 07:01:24 +0100 Subject: [PATCH 04/11] Handle .NET 10 MemoryExtensions.Contains overload with comparer (#37183) Fixes #37176 --- .../Internal/ExpressionTreeFuncletizer.cs | 58 +++++++++++++++---- .../PrimitiveCollectionsQueryCosmosTest.cs | 44 ++++++++++++++ .../PrimitiveCollectionsQueryTestBase.cs | 28 +++++++++ ...imitiveCollectionsQueryOldSqlServerTest.cs | 39 +++++++++++++ ...imitiveCollectionsQuerySqlServer160Test.cs | 39 +++++++++++++ ...veCollectionsQuerySqlServerJsonTypeTest.cs | 39 +++++++++++++ .../PrimitiveCollectionsQuerySqlServerTest.cs | 39 +++++++++++++ .../PrimitiveCollectionsQuerySqliteTest.cs | 39 +++++++++++++ 8 files changed, 315 insertions(+), 10 deletions(-) diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs index eb02e70da83..95fa51cb9be 100644 --- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs +++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs @@ -115,6 +115,9 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor private static readonly bool UseOldBehavior35100 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100; + private static readonly bool UseOldBehavior37176 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37176", out var enabled37176) && enabled37176; + private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection).GetProperties() .Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!; @@ -985,7 +988,9 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall) switch (method.Name) { case nameof(MemoryExtensions.Contains) - when methodCall.Arguments is [var arg0, var arg1] && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0): + when UseOldBehavior37176 + && methodCall.Arguments is [var arg0, var arg1] + && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0): { return Visit( Call( @@ -993,6 +998,22 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall) unwrappedArg0, arg1)); } + // In .NET 10, MemoryExtensions.Contains has an overload that accepts a third, optional comparer, in addition to the older + // overload that accepts two parameters only. + case nameof(MemoryExtensions.Contains) + when !UseOldBehavior37176 + && methodCall.Arguments is [var spanArg, var valueArg, ..] + && (methodCall.Arguments.Count is 2 + || methodCall.Arguments.Count is 3 + && methodCall.Arguments[2] is ConstantExpression { Value: null }) + && TryUnwrapSpanImplicitCast(spanArg, out var unwrappedSpanArg): + { + return Visit( + Call( + EnumerableMethods.Contains.MakeGenericMethod(method.GetGenericArguments()[0]), + unwrappedSpanArg, valueArg)); + } + case nameof(MemoryExtensions.SequenceEqual) when methodCall.Arguments is [var arg0, var arg1] && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0) @@ -1005,20 +1026,37 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall) static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result) { - if (expression is MethodCallExpression + switch (expression) + { + // With newer versions of the SDK, the implicit cast is represented as a MethodCallExpression; + // with older versions, it's a Convert node. + case MethodCallExpression { Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType }, Arguments: [var unwrapped] + } when implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition + && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)): + { + result = unwrapped; + return true; } - && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition - && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>))) - { - result = unwrapped; - return true; - } - result = null; - return false; + case UnaryExpression + { + NodeType: ExpressionType.Convert, + Operand: var unwrapped, + Type: { IsGenericType: true } convertType + } when !UseOldBehavior37176 && convertType.GetGenericTypeDefinition() is var genericTypeDefinition + && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)): + { + result = unwrapped; + return true; + } + + default: + result = null; + return false; + } } } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs index ea203a234b2..b8f411bcbb7 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs @@ -997,6 +997,50 @@ FROM root c """); }); + public override Task Contains_on_Enumerable(bool async) + => CosmosTestHelpers.Instance.NoSyncTest( + async, async a => + { + await base.Contains_on_Enumerable(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE c["Int"] IN (10, 999) +"""); + }); + + public override Task Contains_on_MemoryExtensions(bool async) + => CosmosTestHelpers.Instance.NoSyncTest( + async, async a => + { + await base.Contains_on_MemoryExtensions(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE c["Int"] IN (10, 999) +"""); + }); + +#if NET10_0_OR_GREATER + public override Task Contains_with_MemoryExtensions_with_null_comparer(bool async) + => CosmosTestHelpers.Instance.NoSyncTest( + async, async a => + { + await base.Contains_with_MemoryExtensions_with_null_comparer(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE c["Int"] IN (10, 999) +"""); + }); +#endif + public override Task Column_collection_Length(bool async) => CosmosTestHelpers.Instance.NoSyncTest( async, async a => diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs index 60e7cd24697..10bc9225e49 100644 --- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs @@ -607,6 +607,34 @@ public virtual Task Column_collection_of_bools_Contains(bool async) async, ss => ss.Set().Where(c => c.Bools.Contains(true))); + // C# 14 first-class spans caused MemoryExtensions.Contains to get resolved instead of Enumerable.Contains. + // The following tests that the various overloads are all supported. + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_on_Enumerable(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => Enumerable.Contains(new[] { 10, 999 }, c.Int))); + + // C# 14 first-class spans caused MemoryExtensions.Contains to get resolved instead of Enumerable.Contains. + // The following tests that the various overloads are all supported. + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_on_MemoryExtensions(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => MemoryExtensions.Contains(new[] { 10, 999 }, c.Int))); + + // Note that we don't test EF 8/9 with .NET 10; this test is here for completeness/documentation purposes. +#if NET10_0_OR_GREATER + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_with_MemoryExtensions_with_null_comparer(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => MemoryExtensions.Contains(new[] { 10, 999 }, c.Int, comparer: null))); +#endif + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Column_collection_Count_method(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs index 1041c1e10c1..a492f97fd88 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs @@ -761,6 +761,45 @@ await context.Database.SqlQuery($"SELECT [Bools] AS [Value] FROM [Primit .SingleAsync()); } + public override async Task Contains_on_Enumerable(bool async) + { + await base.Contains_on_Enumerable(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + + + public override async Task Contains_on_MemoryExtensions(bool async) + { + await base.Contains_on_MemoryExtensions(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + +#if NET10_0_OR_GREATER + public override async Task Contains_with_MemoryExtensions_with_null_comparer(bool async) + { + await base.Contains_with_MemoryExtensions_with_null_comparer(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } +#endif + public override Task Column_collection_Count_method(bool async) => AssertCompatibilityLevelTooLow(() => base.Column_collection_Count_method(async)); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs index 9aea6357ba3..186799afd4e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs @@ -955,6 +955,45 @@ await context.Database.SqlQuery($"SELECT [Bools] AS [Value] FROM [Primit .SingleAsync()); } + public override async Task Contains_on_Enumerable(bool async) + { + await base.Contains_on_Enumerable(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + + + public override async Task Contains_on_MemoryExtensions(bool async) + { + await base.Contains_on_MemoryExtensions(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + +#if NET10_0_OR_GREATER + public override async Task Contains_with_MemoryExtensions_with_null_comparer(bool async) + { + await base.Contains_with_MemoryExtensions_with_null_comparer(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } +#endif + public override async Task Column_collection_Count_method(bool async) { await base.Column_collection_Count_method(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs index 29656d04fe6..0e27003a1e2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs @@ -929,6 +929,45 @@ await context.Database.SqlQuery($"SELECT [Bools] AS [Value] FROM [Primit .SingleAsync()); } + public override async Task Contains_on_Enumerable(bool async) + { + await base.Contains_on_Enumerable(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + + + public override async Task Contains_on_MemoryExtensions(bool async) + { + await base.Contains_on_MemoryExtensions(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + +#if NET10_0_OR_GREATER + public override async Task Contains_with_MemoryExtensions_with_null_comparer(bool async) + { + await base.Contains_with_MemoryExtensions_with_null_comparer(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } +#endif + public override async Task Column_collection_Count_method(bool async) { await base.Column_collection_Count_method(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs index 0708539aaa5..ed20eb7db20 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs @@ -978,6 +978,45 @@ await context.Database.SqlQuery($"SELECT [Bools] AS [Value] FROM [Primit .SingleAsync()); } + public override async Task Contains_on_Enumerable(bool async) + { + await base.Contains_on_Enumerable(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + + + public override async Task Contains_on_MemoryExtensions(bool async) + { + await base.Contains_on_MemoryExtensions(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + +#if NET10_0_OR_GREATER + public override async Task Contains_with_MemoryExtensions_with_null_comparer(bool async) + { + await base.Contains_with_MemoryExtensions_with_null_comparer(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } +#endif + public override async Task Column_collection_Count_method(bool async) { await base.Column_collection_Count_method(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs index 58b6bb73a75..68a914a22c5 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs @@ -957,6 +957,45 @@ FROM json_each("p"."Bools") AS "b" """); } + public override async Task Contains_on_Enumerable(bool async) + { + await base.Contains_on_Enumerable(async); + + 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"."String", "p"."Strings" +FROM "PrimitiveCollectionsEntity" AS "p" +WHERE "p"."Int" IN (10, 999) +"""); + } + + + public override async Task Contains_on_MemoryExtensions(bool async) + { + await base.Contains_on_MemoryExtensions(async); + + 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"."String", "p"."Strings" +FROM "PrimitiveCollectionsEntity" AS "p" +WHERE "p"."Int" IN (10, 999) +"""); + } + +#if NET10_0_OR_GREATER + public override async Task Contains_with_MemoryExtensions_with_null_comparer(bool async) + { + await base.Contains_with_MemoryExtensions_with_null_comparer(async); + + 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"."String", "p"."Strings" +FROM "PrimitiveCollectionsEntity" AS "p" +WHERE "p"."Int" IN (10, 999) +"""); + } +#endif + public override async Task Column_collection_Count_method(bool async) { await base.Column_collection_Count_method(async); From d97ff31405cbdabe3309800f384383cfb93e5286 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 19 Nov 2025 07:01:33 +0100 Subject: [PATCH 05/11] Handle .NET 10 MemoryExtensions.Contains overload with comparer (#37182) Fixes #37176 --- .../ParameterExtractingExpressionVisitor.cs | 59 +++++++++++++++---- .../PrimitiveCollectionsQueryTestBase.cs | 28 +++++++++ ...imitiveCollectionsQueryOldSqlServerTest.cs | 39 ++++++++++++ .../PrimitiveCollectionsQuerySqlServerTest.cs | 39 ++++++++++++ .../PrimitiveCollectionsQuerySqliteTest.cs | 39 ++++++++++++ 5 files changed, 193 insertions(+), 11 deletions(-) diff --git a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs b/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs index 410f2dd2839..ee3b82baf90 100644 --- a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs @@ -34,6 +34,9 @@ public class ParameterExtractingExpressionVisitor : ExpressionVisitor private static readonly bool UseOldBehavior35100 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100; + private static readonly bool UseOldBehavior37176 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37176", out var enabled37176) && enabled37176; + /// /// 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 @@ -210,8 +213,9 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp switch (method.Name) { case nameof(MemoryExtensions.Contains) - when methodCallExpression.Arguments is [var arg0, var arg1] && - TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0): + when UseOldBehavior37176 + && methodCallExpression.Arguments is [var arg0, var arg1] + && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0): { return Visit( Expression.Call( @@ -219,6 +223,22 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp unwrappedArg0, arg1)); } + // In .NET 10, MemoryExtensions.Contains has an overload that accepts a third, optional comparer, in addition to the older + // overload that accepts two parameters only. + case nameof(MemoryExtensions.Contains) + when !UseOldBehavior37176 + && methodCallExpression.Arguments is [var spanArg, var valueArg, ..] + && (methodCallExpression.Arguments.Count is 2 + || methodCallExpression.Arguments.Count is 3 + && methodCallExpression.Arguments[2] is ConstantExpression { Value: null }) + && TryUnwrapSpanImplicitCast(spanArg, out var unwrappedSpanArg): + { + return Visit( + Expression.Call( + EnumerableMethods.Contains.MakeGenericMethod(method.GetGenericArguments()[0]), + unwrappedSpanArg, valueArg)); + } + case nameof(MemoryExtensions.SequenceEqual) when methodCallExpression.Arguments is [var arg0, var arg1] && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0) @@ -231,20 +251,37 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result) { - if (expression is MethodCallExpression + switch (expression) + { + // With newer versions of the SDK, the implicit cast is represented as a MethodCallExpression; + // with older versions, it's a Convert node. + case MethodCallExpression { Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType }, Arguments: [var unwrapped] + } when implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition + && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)): + { + result = unwrapped; + return true; } - && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition - && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>))) - { - result = unwrapped; - return true; - } - result = null; - return false; + case UnaryExpression + { + NodeType: ExpressionType.Convert, + Operand: var unwrapped, + Type: { IsGenericType: true } convertType + } when !UseOldBehavior37176 && convertType.GetGenericTypeDefinition() is var genericTypeDefinition + && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)): + { + result = unwrapped; + return true; + } + + default: + result = null; + return false; + } } } diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs index 865f5dc026e..521a504a2c7 100644 --- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs @@ -371,6 +371,34 @@ public virtual Task Column_collection_of_bools_Contains(bool async) async, ss => ss.Set().Where(c => c.Bools.Contains(true))); + // C# 14 first-class spans caused MemoryExtensions.Contains to get resolved instead of Enumerable.Contains. + // The following tests that the various overloads are all supported. + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_on_Enumerable(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => Enumerable.Contains(new[] { 10, 999 }, c.Int))); + + // C# 14 first-class spans caused MemoryExtensions.Contains to get resolved instead of Enumerable.Contains. + // The following tests that the various overloads are all supported. + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_on_MemoryExtensions(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => MemoryExtensions.Contains(new[] { 10, 999 }, c.Int))); + + // Note that we don't test EF 8/9 with .NET 10; this test is here for completeness/documentation purposes. +#if NET10_0_OR_GREATER + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_with_MemoryExtensions_with_null_comparer(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => MemoryExtensions.Contains(new[] { 10, 999 }, c.Int, comparer: null))); +#endif + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Column_collection_Count_method(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs index 222b2e9f8c4..e9d34754632 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs @@ -462,6 +462,45 @@ await context.Database.SqlQuery($"SELECT [Bools] AS [Value] FROM [Primit .SingleAsync()); } + public override async Task Contains_on_Enumerable(bool async) + { + await base.Contains_on_Enumerable(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + + + public override async Task Contains_on_MemoryExtensions(bool async) + { + await base.Contains_on_MemoryExtensions(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + +#if NET10_0_OR_GREATER + public override async Task Contains_with_MemoryExtensions_with_null_comparer(bool async) + { + await base.Contains_with_MemoryExtensions_with_null_comparer(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } +#endif + public override Task Column_collection_Count_method(bool async) => AssertCompatibilityLevelTooLow(() => base.Column_collection_Count_method(async)); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs index 837646b257c..6d604c275e5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs @@ -635,6 +635,45 @@ await context.Database.SqlQuery($"SELECT [Bools] AS [Value] FROM [Primit .SingleAsync()); } + public override async Task Contains_on_Enumerable(bool async) + { + await base.Contains_on_Enumerable(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + + + public override async Task Contains_on_MemoryExtensions(bool async) + { + await base.Contains_on_MemoryExtensions(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } + +#if NET10_0_OR_GREATER + public override async Task Contains_with_MemoryExtensions_with_null_comparer(bool async) + { + await base.Contains_with_MemoryExtensions_with_null_comparer(async); + + 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].[String], [p].[Strings] +FROM [PrimitiveCollectionsEntity] AS [p] +WHERE [p].[Int] IN (10, 999) +"""); + } +#endif + public override async Task Column_collection_Count_method(bool async) { await base.Column_collection_Count_method(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs index 9faa7597b62..a9c9d12e50f 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs @@ -625,6 +625,45 @@ FROM json_each("p"."Bools") AS "b" """); } + public override async Task Contains_on_Enumerable(bool async) + { + await base.Contains_on_Enumerable(async); + + 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"."String", "p"."Strings" +FROM "PrimitiveCollectionsEntity" AS "p" +WHERE "p"."Int" IN (10, 999) +"""); + } + + + public override async Task Contains_on_MemoryExtensions(bool async) + { + await base.Contains_on_MemoryExtensions(async); + + 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"."String", "p"."Strings" +FROM "PrimitiveCollectionsEntity" AS "p" +WHERE "p"."Int" IN (10, 999) +"""); + } + +#if NET10_0_OR_GREATER + public override async Task Contains_with_MemoryExtensions_with_null_comparer(bool async) + { + await base.Contains_with_MemoryExtensions_with_null_comparer(async); + + 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"."String", "p"."Strings" +FROM "PrimitiveCollectionsEntity" AS "p" +WHERE "p"."Int" IN (10, 999) +"""); + } +#endif + public override async Task Column_collection_Count_method(bool async) { await base.Column_collection_Count_method(async); From 662a7083d1c81c7adc92d01b728cf7a22d17e262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Cincura=20=E2=86=B9?= Date: Wed, 19 Nov 2025 18:49:09 +0100 Subject: [PATCH 06/11] Update to Mac 15 queues. (#37097) --- azure-pipelines-public.yml | 4 ++-- azure-pipelines.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines-public.yml b/azure-pipelines-public.yml index 98c9420f928..0cda45d0c05 100644 --- a/azure-pipelines-public.yml +++ b/azure-pipelines-public.yml @@ -176,7 +176,7 @@ stages: - job: macOS enablePublishTestResults: true pool: - vmImage: macOS-13 + vmImage: macOS-15 variables: # Rely on task Arcade injects, not auto-injected build step. - skipComponentGovernanceDetection: true @@ -269,7 +269,7 @@ stages: value: $(_BuildConfig) - ${{ if eq(variables['System.TeamProject'], 'public') }}: - name: HelixTargetQueues - value: Windows.10.Amd64.Open;OSX.13.Amd64.Open;OSX.13.ARM64.Open;Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64 + value: Windows.10.Amd64.Open;OSX.15.Amd64.Open;OSX.15.ARM64.Open;Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64 - name: _HelixAccessToken value: '' # Needed for public queues - ${{ if ne(variables['System.TeamProject'], 'public') }}: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0a45718fc81..30a31e3a5cb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -135,7 +135,7 @@ extends: - job: macOS pool: name: Azure Pipelines - image: macOS-13 + image: macOS-15 os: macOS variables: # Rely on task Arcade injects, not auto-injected build step. From 00b6a646a740a4d6a9cffc606f6c176f215fb686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Cincura=20=E2=86=B9?= Date: Wed, 19 Nov 2025 18:49:17 +0100 Subject: [PATCH 07/11] Update to Mac 15 queues. (#37098) --- azure-pipelines-public.yml | 4 ++-- azure-pipelines.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/azure-pipelines-public.yml b/azure-pipelines-public.yml index c42e450fb7e..e5d71520fdd 100644 --- a/azure-pipelines-public.yml +++ b/azure-pipelines-public.yml @@ -93,7 +93,7 @@ stages: - job: macOS enablePublishTestResults: true pool: - vmImage: macOS-13 + vmImage: macOS-15 variables: # Rely on task Arcade injects, not auto-injected build step. - skipComponentGovernanceDetection: true @@ -151,7 +151,7 @@ stages: - name: _HelixBuildConfig value: $(_BuildConfig) - name: HelixTargetQueues - value: Windows.10.Amd64.Open;OSX.13.ARM64.Open;Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64 + value: Windows.10.Amd64.Open;OSX.15.ARM64.Open;Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64 - name: _HelixAccessToken value: '' # Needed for public queues steps: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 017dc6fadf5..f0c87f45726 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -140,7 +140,7 @@ extends: - job: macOS pool: name: Azure Pipelines - image: macOS-13 + image: macOS-15 os: macOS variables: # Rely on task Arcade injects, not auto-injected build step. @@ -195,7 +195,7 @@ extends: - name: _HelixBuildConfig value: $(_BuildConfig) - name: HelixTargetQueues - value: Windows.10.Amd64;OSX.13.ARM64;Ubuntu.2204.Amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64 + value: Windows.10.Amd64;OSX.15.ARM64;Ubuntu.2204.Amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64 - name: _HelixAccessToken # Needed for internal queues value: $(HelixApiAccessToken) From 3532857f20a32f2eee735cb5aa72ea12d44278e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 00:15:47 +0000 Subject: [PATCH 08/11] Fix snapshot generation to capture column type for JSON columns Port of #37284 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Design/CSharpSnapshotGenerator.cs | 15 ++++ ...rpMigrationsGeneratorTest.ModelSnapshot.cs | 82 ++++++++++++++++++- .../Migrations/SqlServerModelDifferTest.cs | 53 ++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index d5efd7f6a81..5b1ba37c024 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text; +using System.Text.Json; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -837,6 +838,20 @@ protected virtual void GenerateEntityTypeAnnotations( .FilterIgnoredAnnotations(entityType.GetAnnotations()) .ToDictionary(a => a.Name, a => a); + // Add ContainerColumnType annotation if entity is mapped to JSON but the type annotation is missing + if (annotations.ContainsKey(RelationalAnnotationNames.ContainerColumnName) + && !annotations.ContainsKey(RelationalAnnotationNames.ContainerColumnType)) + { + var containerColumnType = entityType.GetContainerColumnType() + ?? Dependencies.RelationalTypeMappingSource.FindMapping(typeof(JsonElement))?.StoreType; + if (containerColumnType != null) + { + annotations[RelationalAnnotationNames.ContainerColumnType] = new Annotation( + RelationalAnnotationNames.ContainerColumnType, + containerColumnType); + } + } + GenerateTableMapping(entityTypeBuilderName, entityType, stringBuilder, annotations); GenerateSplitTableMapping(entityTypeBuilderName, entityType, stringBuilder); diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs index ce667239197..4d82613e507 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs @@ -4362,7 +4362,9 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot() b1.ToTable("EntityWithOneProperty", "DefaultSchema"); - b1.ToJson("EntityWithTwoProperties"); + b1 + .ToJson("EntityWithTwoProperties") + .HasColumnType("nvarchar(max)"); b1.WithOwner("EntityWithOneProperty") .HasForeignKey("EntityWithOnePropertyId"); @@ -4437,6 +4439,7 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot() Assert.Equal(nameof(EntityWithOneProperty), ownedType1.GetTableName()); Assert.Equal("EntityWithTwoProperties", ownedType1.GetContainerColumnName()); + Assert.Equal("nvarchar(max)", ownedType1.GetContainerColumnType()); var ownership2 = ownedType1.FindNavigation(nameof(EntityWithStringKey)).ForeignKey; Assert.Equal("EntityWithTwoPropertiesEntityWithOnePropertyId", ownership2.Properties[0].Name); @@ -4473,6 +4476,83 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot() Assert.Equal("Name", ownedProperties3[3].Name); }); + [ConditionalFact] + public virtual void Owned_types_mapped_to_json_with_explicit_column_type_are_stored_in_snapshot() + => Test( + builder => + { + builder.Entity(b => + { + b.HasKey(x => x.Id).HasName("PK_Custom"); + + b.OwnsOne( + x => x.EntityWithTwoProperties, bb => + { + bb.ToJson().HasColumnType("json"); + bb.Ignore(x => x.Id); + bb.Property(x => x.AlternateId).HasJsonPropertyName("NotKey"); + bb.WithOwner(e => e.EntityWithOneProperty); + }); + }); + }, + AddBoilerPlate( + GetHeading() + + """ + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.HasKey("Id") + .HasName("PK_Custom"); + + b.ToTable("EntityWithOneProperty", "DefaultSchema"); + }); + + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty", b => + { + b.OwnsOne("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithTwoProperties", "EntityWithTwoProperties", b1 => + { + b1.Property("EntityWithOnePropertyId") + .HasColumnType("int"); + + b1.Property("AlternateId") + .HasColumnType("int") + .HasAnnotation("Relational:JsonPropertyName", "NotKey"); + + b1.HasKey("EntityWithOnePropertyId"); + + b1.ToTable("EntityWithOneProperty", "DefaultSchema"); + + b1 + .ToJson("EntityWithTwoProperties") + .HasColumnType("json"); + + b1.WithOwner("EntityWithOneProperty") + .HasForeignKey("EntityWithOnePropertyId"); + + b1.Navigation("EntityWithOneProperty"); + }); + + b.Navigation("EntityWithTwoProperties"); + }); +""", usingSystem: false), + o => + { + var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty)); + Assert.Equal("PK_Custom", entityWithOneProperty.GetKeys().Single().GetName()); + + var ownership1 = entityWithOneProperty.FindNavigation(nameof(EntityWithOneProperty.EntityWithTwoProperties)) + .ForeignKey; + var ownedType1 = ownership1.DeclaringEntityType; + Assert.Equal(nameof(EntityWithOneProperty), ownedType1.GetTableName()); + Assert.Equal("EntityWithTwoProperties", ownedType1.GetContainerColumnName()); + Assert.Equal("json", ownedType1.GetContainerColumnType()); + }); + private class Order { public int Id { get; set; } diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs index 0f8dddae5a4..4b180344c3c 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs @@ -1900,4 +1900,57 @@ public void Rebuild_index_with_different_datacompression_value() Assert.Equal(DataCompressionType.Page, annotationValue); }); + + [ConditionalFact] + public void Alter_column_from_nvarchar_max_to_json_for_owned_type() + => Execute( + _ => { }, + source => source.Entity( + "Blog", + x => + { + x.Property("Id"); + x.HasKey("Id"); + x.OwnsOne( + "Details", "Details", d => + { + d.Property("Author"); + d.Property("Viewers"); + d.ToJson(); + }); + }), + target => target.Entity( + "Blog", + x => + { + x.Property("Id"); + x.HasKey("Id"); + x.OwnsOne( + "Details", "Details", d => + { + d.Property("Author"); + d.Property("Viewers"); + d.ToJson().HasColumnType("json"); + }); + }), + upOps => + { + Assert.Equal(1, upOps.Count); + + var operation = Assert.IsType(upOps[0]); + Assert.Equal("Blog", operation.Table); + Assert.Equal("Details", operation.Name); + Assert.Equal("json", operation.ColumnType); + Assert.Equal("nvarchar(max)", operation.OldColumn.ColumnType); + }, + downOps => + { + Assert.Equal(1, downOps.Count); + + var operation = Assert.IsType(downOps[0]); + Assert.Equal("Blog", operation.Table); + Assert.Equal("Details", operation.Name); + Assert.Equal("nvarchar(max)", operation.ColumnType); + Assert.Equal("json", operation.OldColumn.ColumnType); + }); } From 2cc56d3912a43dd0710a61aca69a55635ee9729f Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 06:02:43 +0000 Subject: [PATCH 09/11] Update dependencies from https://github.com/dotnet/arcade build 20251127.5 (#37315) [release/9.0] Update dependencies from dotnet/arcade --- NuGet.config | 2 -- eng/Version.Details.xml | 12 ++++++------ eng/Versions.props | 2 +- eng/common/core-templates/job/source-build.yml | 2 +- eng/common/core-templates/steps/source-build.yml | 2 +- eng/common/cross/arm/sources.list.focal | 11 ----------- eng/common/cross/arm/sources.list.jammy | 11 ----------- eng/common/cross/arm64/sources.list.focal | 11 ----------- eng/common/cross/arm64/sources.list.jammy | 11 ----------- eng/common/cross/x64/sources.list.bionic | 11 ----------- eng/common/cross/x64/sources.list.xenial | 11 ----------- eng/common/cross/x86/sources.list.focal | 11 ----------- eng/common/cross/x86/sources.list.jammy | 11 ----------- eng/common/templates/post-build/post-build.yml | 2 +- global.json | 4 ++-- 15 files changed, 12 insertions(+), 102 deletions(-) delete mode 100644 eng/common/cross/arm/sources.list.focal delete mode 100644 eng/common/cross/arm/sources.list.jammy delete mode 100644 eng/common/cross/arm64/sources.list.focal delete mode 100644 eng/common/cross/arm64/sources.list.jammy delete mode 100644 eng/common/cross/x64/sources.list.bionic delete mode 100644 eng/common/cross/x64/sources.list.xenial delete mode 100644 eng/common/cross/x86/sources.list.focal delete mode 100644 eng/common/cross/x86/sources.list.jammy diff --git a/NuGet.config b/NuGet.config index 45ab02a8f5b..d1a8a417e43 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,7 +4,6 @@ - @@ -21,7 +20,6 @@ - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 664f088eb44..8a5f81e4b02 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -67,17 +67,17 @@ - + https://github.com/dotnet/arcade - 6e2d8e204cebac7d3989c1996f96e5a9ed63fa80 + 0890ca08513391dafe556fb326c73c6c5c6cb329 - + https://github.com/dotnet/arcade - 6e2d8e204cebac7d3989c1996f96e5a9ed63fa80 + 0890ca08513391dafe556fb326c73c6c5c6cb329 - + https://github.com/dotnet/arcade - 6e2d8e204cebac7d3989c1996f96e5a9ed63fa80 + 0890ca08513391dafe556fb326c73c6c5c6cb329 diff --git a/eng/Versions.props b/eng/Versions.props index 47c6ed03b2a..aaf5ac026dc 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -34,7 +34,7 @@ 9.0.11 - 9.0.0-beta.25562.4 + 9.0.0-beta.25577.5 17.8.43 diff --git a/eng/common/core-templates/job/source-build.yml b/eng/common/core-templates/job/source-build.yml index 5baedac1e03..1037ccedcb5 100644 --- a/eng/common/core-templates/job/source-build.yml +++ b/eng/common/core-templates/job/source-build.yml @@ -65,7 +65,7 @@ jobs: demands: ImageOverride -equals build.ubuntu.2004.amd64 ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] - image: 1es-mariner-2 + image: 1es-azurelinux-3 os: linux ${{ else }}: pool: diff --git a/eng/common/core-templates/steps/source-build.yml b/eng/common/core-templates/steps/source-build.yml index 0718e4ba902..7846584d2a7 100644 --- a/eng/common/core-templates/steps/source-build.yml +++ b/eng/common/core-templates/steps/source-build.yml @@ -41,7 +41,7 @@ steps: # in the default public locations. internalRuntimeDownloadArgs= if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then - internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://dotnetbuilds.blob.core.windows.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' + internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://ci.dot.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://ci.dot.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' fi buildConfig=Release diff --git a/eng/common/cross/arm/sources.list.focal b/eng/common/cross/arm/sources.list.focal deleted file mode 100644 index 4de2600c174..00000000000 --- a/eng/common/cross/arm/sources.list.focal +++ /dev/null @@ -1,11 +0,0 @@ -deb http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe -deb-src http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe - -deb http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe -deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe - -deb http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted -deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted - -deb http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse -deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse diff --git a/eng/common/cross/arm/sources.list.jammy b/eng/common/cross/arm/sources.list.jammy deleted file mode 100644 index 6bb0453029c..00000000000 --- a/eng/common/cross/arm/sources.list.jammy +++ /dev/null @@ -1,11 +0,0 @@ -deb http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe -deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe - -deb http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe -deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe - -deb http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted -deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted - -deb http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse -deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse diff --git a/eng/common/cross/arm64/sources.list.focal b/eng/common/cross/arm64/sources.list.focal deleted file mode 100644 index 4de2600c174..00000000000 --- a/eng/common/cross/arm64/sources.list.focal +++ /dev/null @@ -1,11 +0,0 @@ -deb http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe -deb-src http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe - -deb http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe -deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe - -deb http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted -deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted - -deb http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse -deb-src http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted universe multiverse diff --git a/eng/common/cross/arm64/sources.list.jammy b/eng/common/cross/arm64/sources.list.jammy deleted file mode 100644 index 6bb0453029c..00000000000 --- a/eng/common/cross/arm64/sources.list.jammy +++ /dev/null @@ -1,11 +0,0 @@ -deb http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe -deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted universe - -deb http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe -deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted universe - -deb http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted -deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted - -deb http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse -deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy-security main restricted universe multiverse diff --git a/eng/common/cross/x64/sources.list.bionic b/eng/common/cross/x64/sources.list.bionic deleted file mode 100644 index a71ccadcffa..00000000000 --- a/eng/common/cross/x64/sources.list.bionic +++ /dev/null @@ -1,11 +0,0 @@ -deb http://archive.ubuntu.com/ubuntu/ bionic main restricted universe -deb-src http://archive.ubuntu.com/ubuntu/ bionic main restricted universe - -deb http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe -deb-src http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe - -deb http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted -deb-src http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted - -deb http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse -deb-src http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/x64/sources.list.xenial b/eng/common/cross/x64/sources.list.xenial deleted file mode 100644 index ad9c5a0144e..00000000000 --- a/eng/common/cross/x64/sources.list.xenial +++ /dev/null @@ -1,11 +0,0 @@ -deb http://archive.ubuntu.com/ubuntu/ xenial main restricted universe -deb-src http://archive.ubuntu.com/ubuntu/ xenial main restricted universe - -deb http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe -deb-src http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe - -deb http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted -deb-src http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted - -deb http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse -deb-src http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse diff --git a/eng/common/cross/x86/sources.list.focal b/eng/common/cross/x86/sources.list.focal deleted file mode 100644 index 99d5731330e..00000000000 --- a/eng/common/cross/x86/sources.list.focal +++ /dev/null @@ -1,11 +0,0 @@ -deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe -deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted universe - -deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe -deb-src http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe - -deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted -deb-src http://archive.ubuntu.com/ubuntu/ focal-backports main restricted - -deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse -deb-src http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse diff --git a/eng/common/cross/x86/sources.list.jammy b/eng/common/cross/x86/sources.list.jammy deleted file mode 100644 index af1c1feaeac..00000000000 --- a/eng/common/cross/x86/sources.list.jammy +++ /dev/null @@ -1,11 +0,0 @@ -deb http://archive.ubuntu.com/ubuntu/ jammy main restricted universe -deb-src http://archive.ubuntu.com/ubuntu/ jammy main restricted universe - -deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted universe -deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted universe - -deb http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted -deb-src http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted - -deb http://archive.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse -deb-src http://archive.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml index ef8cf549113..53ede714bdd 100644 --- a/eng/common/templates/post-build/post-build.yml +++ b/eng/common/templates/post-build/post-build.yml @@ -5,4 +5,4 @@ stages: is1ESPipeline: false ${{ each parameter in parameters }}: - ${{ parameter.key }}: ${{ parameter.value }} + ${{ parameter.key }}: ${{ parameter.value }} \ No newline at end of file diff --git a/global.json b/global.json index 2b9a9a5500b..dcc285dbd61 100644 --- a/global.json +++ b/global.json @@ -13,7 +13,7 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25562.4", - "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25562.4" + "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25577.5", + "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25577.5" } } From b11390785928acf6e708cb00f6e0ed171492a75d Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 8 Dec 2025 12:45:03 -0800 Subject: [PATCH 10/11] Remove quirk references --- src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs index aa555620a7e..32d5cd5d11b 100644 --- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs +++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs @@ -986,8 +986,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall) // In .NET 10, MemoryExtensions.Contains has an overload that accepts a third, optional comparer, in addition to the older // overload that accepts two parameters only. case nameof(MemoryExtensions.Contains) - when !UseOldBehavior37176 - && methodCall.Arguments is [var spanArg, var valueArg, ..] + when methodCall.Arguments is [var spanArg, var valueArg, ..] && (methodCall.Arguments.Count is 2 || methodCall.Arguments.Count is 3 && methodCall.Arguments[2] is ConstantExpression { Value: null }) @@ -1031,7 +1030,7 @@ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] NodeType: ExpressionType.Convert, Operand: var unwrapped, Type: { IsGenericType: true } convertType - } when !UseOldBehavior37176 && convertType.GetGenericTypeDefinition() is var genericTypeDefinition + } when convertType.GetGenericTypeDefinition() is var genericTypeDefinition && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)): { result = unwrapped; From 2656fdbac9c416b969697389417bfa587ef690b1 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 9 Dec 2025 13:34:56 -0800 Subject: [PATCH 11/11] Fix baselines --- .../Design/CSharpSnapshotGenerator.cs | 1 - .../Internal/ExpressionTreeFuncletizer.cs | 29 --------------- .../PrimitiveCollectionsQueryCosmosTest.cs | 36 +++++++++++++++++++ ...imitiveCollectionsQueryOldSqlServerTest.cs | 6 ++-- ...imitiveCollectionsQuerySqlServer160Test.cs | 6 ++-- ...veCollectionsQuerySqlServerJsonTypeTest.cs | 14 ++++---- .../PrimitiveCollectionsQuerySqlServerTest.cs | 6 ++-- .../PrimitiveCollectionsQuerySqliteTest.cs | 6 ++-- 8 files changed, 55 insertions(+), 49 deletions(-) diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index c457c1c7677..f46d2e27a2b 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text; -using System.Text.Json; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs index 32d5cd5d11b..976d4b8d57f 100644 --- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs +++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs @@ -983,21 +983,6 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall) unwrappedSpanArg, valueArg)); } - // In .NET 10, MemoryExtensions.Contains has an overload that accepts a third, optional comparer, in addition to the older - // overload that accepts two parameters only. - case nameof(MemoryExtensions.Contains) - when methodCall.Arguments is [var spanArg, var valueArg, ..] - && (methodCall.Arguments.Count is 2 - || methodCall.Arguments.Count is 3 - && methodCall.Arguments[2] is ConstantExpression { Value: null }) - && TryUnwrapSpanImplicitCast(spanArg, out var unwrappedSpanArg): - { - return Visit( - Call( - EnumerableMethods.Contains.MakeGenericMethod(method.GetGenericArguments()[0]), - unwrappedSpanArg, valueArg)); - } - case nameof(MemoryExtensions.SequenceEqual) when methodCall.Arguments is [var spanArg, var otherArg] && TryUnwrapSpanImplicitCast(spanArg, out var unwrappedSpanArg) @@ -1012,8 +997,6 @@ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] { switch (expression) { - // With newer versions of the SDK, the implicit cast is represented as a MethodCallExpression; - // with older versions, it's a Convert node. case MethodCallExpression { Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType }, @@ -1025,18 +1008,6 @@ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] return true; } - case UnaryExpression - { - NodeType: ExpressionType.Convert, - Operand: var unwrapped, - Type: { IsGenericType: true } convertType - } when convertType.GetGenericTypeDefinition() is var genericTypeDefinition - && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)): - { - result = unwrapped; - return true; - } - default: result = null; return false; diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs index 71cb9e15f11..127a7f2568a 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs @@ -1986,6 +1986,42 @@ await Assert.ThrowsAsync( AssertSql(); } + public override async Task Contains_on_Enumerable() + { + await base.Contains_on_Enumerable(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE c["Int"] IN (10, 999) +"""); + } + + public override async Task Contains_on_MemoryExtensions() + { + await base.Contains_on_MemoryExtensions(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE c["Int"] IN (10, 999) +"""); + } + + public override async Task Contains_with_MemoryExtensions_with_null_comparer() + { + await base.Contains_with_MemoryExtensions_with_null_comparer(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE c["Int"] IN (10, 999) +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs index e2ae4c21563..4b60503d2c9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs @@ -890,7 +890,7 @@ public override async Task Contains_on_Enumerable() 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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); @@ -903,7 +903,7 @@ public override async Task Contains_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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); @@ -915,7 +915,7 @@ public override async Task Contains_with_MemoryExtensions_with_null_comparer() 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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs index 81bba00f950..0e815ebdcb4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs @@ -967,7 +967,7 @@ public override async Task Contains_on_Enumerable() 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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); @@ -980,7 +980,7 @@ public override async Task Contains_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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); @@ -992,7 +992,7 @@ public override async Task Contains_with_MemoryExtensions_with_null_comparer() 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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs index 80593848314..dba48fa2a34 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs @@ -1114,7 +1114,7 @@ public override async Task Contains_on_Enumerable() 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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); @@ -1127,7 +1127,7 @@ public override async Task Contains_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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); @@ -1139,7 +1139,7 @@ public override async Task Contains_with_MemoryExtensions_with_null_comparer() 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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); @@ -1722,8 +1722,8 @@ public override async Task Parameter_collection_Concat_column_collection() AssertSql( """ -@ints1='11' -@ints2='111' +@p1='11' +@p2='111' 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] @@ -1731,10 +1731,10 @@ FROM [PrimitiveCollectionsEntity] AS [p] SELECT COUNT(*) FROM ( SELECT 1 AS empty - FROM (VALUES (@ints1), (@ints2)) AS [i]([Value]) + FROM (VALUES (@p1), (@p2)) AS [p0]([Value]) UNION ALL SELECT 1 AS empty - FROM OPENJSON([p].[Ints]) AS [i0] + FROM OPENJSON([p].[Ints]) AS [i] ) AS [u]) = 2 """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs index a2f2b1bf202..5082de36803 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs @@ -990,7 +990,7 @@ public override async Task Contains_on_Enumerable() 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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); @@ -1002,7 +1002,7 @@ public override async Task Contains_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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); @@ -1014,7 +1014,7 @@ public override async Task Contains_with_MemoryExtensions_with_null_comparer() 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].[String], [p].[Strings] +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 [p].[Int] IN (10, 999) """); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs index 50151024601..f6098426ccb 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs @@ -960,7 +960,7 @@ public override async Task Contains_on_Enumerable() 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"."String", "p"."Strings" +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 "p"."Int" IN (10, 999) """); @@ -973,7 +973,7 @@ public override async Task Contains_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"."String", "p"."Strings" +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 "p"."Int" IN (10, 999) """); @@ -985,7 +985,7 @@ public override async Task Contains_with_MemoryExtensions_with_null_comparer() 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"."String", "p"."Strings" +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 "p"."Int" IN (10, 999) """);