diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs b/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs index e0bf02dda27..472b9394662 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs @@ -274,9 +274,22 @@ protected virtual void SetEntityState(EntityState oldState, EntityState newState // Hot path; do not use LINQ foreach (var property in structuralType.GetFlattenedProperties()) { - if (property.GetAfterSaveBehavior() != PropertySaveBehavior.Save) + var afterSaveBehavior = property.GetAfterSaveBehavior(); + if (afterSaveBehavior != PropertySaveBehavior.Save) { _stateData.FlagProperty(property.GetIndex(), PropertyFlag.Modified, isFlagged: false); + + // Properties with AfterSaveBehavior.Throw were unflagged above, but DetectChanges could + // re-mark them if the original values snapshot doesn't match the current values (e.g. for + // shadow properties on complex types whose snapshot stores default values). + // Set the original values of Throw properties to match current values so that + // DetectChanges won't find a false mismatch and re-mark them as modified. + if (afterSaveBehavior == PropertySaveBehavior.Throw + && property.GetOriginalValueIndex() >= 0 + && !IsConceptualNull(property)) + { + SetOriginalValue(property, this[property], skipChangeDetection: true); + } } // Properties that are not loaded (IsAutoLoaded = false and not yet loaded) should diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/AdHocComplexTypeQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/AdHocComplexTypeQueryCosmosTest.cs index 82d17ce9cee..c27c7efa56b 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/AdHocComplexTypeQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/AdHocComplexTypeQueryCosmosTest.cs @@ -85,6 +85,24 @@ FROM root c """); } + public override async Task Update_entity_with_nullable_complex_type_and_discriminator_does_not_throw() + { + await base.Update_entity_with_nullable_complex_type_and_discriminator_does_not_throw(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +OFFSET 0 LIMIT 2 +""", + // + """ +SELECT VALUE c +FROM root c +OFFSET 0 LIMIT 2 +"""); + } + protected override DbContextOptionsBuilder AddNonSharedOptions(DbContextOptionsBuilder builder) => base.AddNonSharedOptions(builder) .ConfigureWarnings(w => w.Ignore(CosmosEventId.NoPartitionKeyDefined)); diff --git a/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs index b5d5739c8e3..41ab4f94fa5 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs @@ -379,6 +379,59 @@ public class OptionalComplexProperty #endregion Issue37337 + #region Issue38105 + + [ConditionalFact] + public virtual async Task Update_entity_with_nullable_complex_type_and_discriminator_does_not_throw() + { + var contextFactory = await InitializeNonSharedTest( + seed: context => + { + var entity = new Context37337.EntityType + { + Id = Guid.NewGuid(), + Prop = new Context37337.OptionalComplexProperty + { + OptionalValue = true + } + }; + context.Add(entity); + context.Entry(entity).Property(Issue37337CreatedByShadowPropertyName).CurrentValue = "Seeder"; + return context.SaveChangesAsync(); + }); + + await using var context = contextFactory.CreateDbContext(); + + var entity = await context.Set().SingleAsync(); + var id = entity.Id; + context.ChangeTracker.Clear(); + + // Create a new disconnected instance with the same key and Update it. + // The complex type discriminator (shadow property with AfterSaveBehavior.Throw) should not + // be marked as modified by Update(), and SaveChanges should succeed without throwing. + var updatedEntity = new Context37337.EntityType + { + Id = id, + Prop = new Context37337.OptionalComplexProperty + { + OptionalValue = false + } + }; + + context.Update(updatedEntity); + + await context.SaveChangesAsync(); + + context.ChangeTracker.Clear(); + + var reloaded = await context.Set().SingleAsync(); + Assert.Equal(id, reloaded.Id); + Assert.NotNull(reloaded.Prop); + Assert.False(reloaded.Prop.OptionalValue); + } + + #endregion Issue38105 + protected override string NonSharedStoreName => "AdHocComplexTypeQueryTest"; }