From 0ca5560db1a2c95abc7d5060f4e8f8260f5143bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:49:00 +0000 Subject: [PATCH 1/2] Initial plan From 4533af733177fb59ec33388dabf58e8551d4d89a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 18:04:18 +0000 Subject: [PATCH 2/2] Fix complex type shadow discriminator incorrectly marked as modified on Update The fix for #37337 (PR #37394) applied too broadly to all snapshot factories, storing default values for shadow properties on complex types even when reading from IInternalEntry (which can correctly access shadow values). This caused OriginalValuesFactoryFactory to store null instead of the actual discriminator value, making change detection falsely mark the discriminator as modified. Now the default-value behavior only applies when the snapshot factory parameter is not IInternalEntry (e.g., during query materialization via ShadowValuesFactoryFactory where shadow property materialization on complex types is unsupported). Agent-Logs-Url: https://github.com/dotnet/efcore/sessions/1c384fcd-4abb-47ae-b177-e03ff1a12997 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Internal/SnapshotFactoryFactory.cs | 7 ++- .../Query/AdHocComplexTypeQueryTestBase.cs | 56 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs index 3b8be38be64..91980257d8b 100644 --- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs @@ -143,7 +143,12 @@ protected virtual Expression CreateSnapshotExpression( case IProperty property: // Shadow property materialization on complex types is not currently supported (see #35613). - if (!UseOldBehavior37337 && propertyBase.DeclaringType is IComplexType && property.IsShadowProperty()) + // Only use default values when we don't have access to an IInternalEntry (which stores shadow values). + // When reading from IInternalEntry (e.g. OriginalValuesFactoryFactory), shadow values can be read correctly. + if (!UseOldBehavior37337 + && propertyBase.DeclaringType is IComplexType + && property.IsShadowProperty() + && (parameter == null || !parameter.Type.IsAssignableTo(typeof(IInternalEntry)))) { arguments[i] = propertyBase.ClrType.GetDefaultValueConstant(); types[i] = propertyBase.ClrType; diff --git a/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs index b2123250eac..dbda1421be0 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs @@ -401,6 +401,62 @@ public class OptionalComplexProperty #endregion Issue37337 + #region Issue37914 + + [ConditionalFact] + public virtual async Task Update_entity_with_nullable_complex_type_and_discriminator() + { + var contextFactory = await InitializeAsync( + seed: context => + { + context.Add( + new Context37914.EntityType + { + Prop = new Context37914.OptionalComplexProperty + { + OptionalValue = true + } + }); + return context.SaveChangesAsync(); + }); + + await using var context = contextFactory.CreateContext(); + + var entity = await context.Set().SingleAsync(); + Assert.NotNull(entity.Prop); + Assert.True(entity.Prop.OptionalValue); + + context.Update(entity); + await context.SaveChangesAsync(); + } + + private class Context37914(DbContextOptions options) : DbContext(options) + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + var entity = modelBuilder.Entity(); + entity.Property(p => p.Id); + entity.HasKey(p => p.Id); + + var compl = entity.ComplexProperty(p => p.Prop); + compl.Property(p => p.OptionalValue); + compl.HasDiscriminator(); + } + + public class EntityType + { + public Guid Id { get; set; } + public OptionalComplexProperty? Prop { get; set; } + } + + public class OptionalComplexProperty + { + public bool? OptionalValue { get; set; } + } + } + + #endregion Issue37914 + protected override string StoreName => "AdHocComplexTypeQueryTest"; }