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) 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/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/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; } 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.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.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.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); + }); } 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);