diff --git a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs index 6497a886d8b..5d2600518a3 100644 --- a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs +++ b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs @@ -21,6 +21,9 @@ public class ChangeDetector : IChangeDetector private static readonly bool UseOldBehavior37387 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37387", out var enabled) && enabled; + private static readonly bool UseOldBehavior37890 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37890", out var enabled) && enabled; + /// /// 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 @@ -339,9 +342,9 @@ public virtual bool DetectComplexPropertyChange(InternalEntryBase entry, IComple if ((currentValue is null) != (originalValue is null)) { - // If it changed from null to non-null, mark all inner properties as modified + // If it changed from null to non-null or from non-null to null, mark all inner properties as modified // to ensure the entity is detected as modified and the complex type properties are persisted - if (currentValue is not null) + if (!UseOldBehavior37890 || currentValue is not null) { foreach (var innerProperty in complexProperty.ComplexType.GetFlattenedProperties()) { diff --git a/test/EFCore.InMemory.FunctionalTests/ComplexTypesTrackingInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/ComplexTypesTrackingInMemoryTest.cs index 720f9ef74ed..e420a64e5ce 100644 --- a/test/EFCore.InMemory.FunctionalTests/ComplexTypesTrackingInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/ComplexTypesTrackingInMemoryTest.cs @@ -27,6 +27,11 @@ public override Task Can_save_default_values_in_optional_complex_property_with_m // See https://github.com/dotnet/efcore/issues/31464 => Task.CompletedTask; + public override Task Can_null_complex_property_with_default_values_and_multiple_properties(bool async) + // InMemory provider has issues with complex type query compilation and materialization + // See https://github.com/dotnet/efcore/issues/31464 + => Task.CompletedTask; + // Complex type collections are not supported in InMemory provider // See https://github.com/dotnet/efcore/issues/31464 public override Task Can_change_state_from_Deleted_with_complex_collection(EntityState newState, bool async) diff --git a/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs b/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs index 9da17d6ac8f..4cddf3f8cd9 100644 --- a/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs +++ b/test/EFCore.Specification.Tests/ComplexTypesTrackingTestBase.cs @@ -4619,6 +4619,56 @@ await ExecuteWithStrategyInTransactionAsync( }); } + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_null_complex_property_with_default_values_and_multiple_properties(bool async) + { + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var entity = Fixture.UseProxies + ? context.CreateProxy() + : new EntityWithOptionalMultiPropComplex(); + + entity.Id = Guid.NewGuid(); + // Set the complex property with default values + entity.ComplexProp = new MultiPropComplex + { + IntValue = 0, + BoolValue = false, + DateValue = default, + }; + + _ = async ? await context.AddAsync(entity) : context.Add(entity); + _ = async ? await context.SaveChangesAsync() : context.SaveChanges(); + + Assert.NotNull(entity.ComplexProp); + }, + async context => + { + var entity = async + ? await context.Set().SingleAsync() + : context.Set().Single(); + + Assert.NotNull(entity.ComplexProp); + + entity.ComplexProp = null; + + _ = async ? await context.SaveChangesAsync() : context.SaveChanges(); + + Assert.Null(entity.ComplexProp); + }, + async context => + { + var entity = async + ? await context.Set().SingleAsync() + : context.Set().Single(); + + Assert.Null(entity.ComplexProp); + }); + } + public class EntityWithOptionalMultiPropComplex { public virtual Guid Id { get; set; } diff --git a/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs index 336dfb181b9..387decc4724 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs @@ -352,6 +352,10 @@ public override void Can_write_original_values_for_properties_of_complex_propert public override Task Can_save_default_values_in_optional_complex_property_with_multiple_properties(bool async) => Task.CompletedTask; + // Issue #36175: Complex types with notification change tracking are not supported + public override Task Can_null_complex_property_with_default_values_and_multiple_properties(bool async) + => Task.CompletedTask; + // Fields can't be proxied public override Task Can_change_state_from_Deleted_with_complex_field_collection(EntityState newState, bool async) => Task.CompletedTask;