efcore icon indicating copy to clipboard operation
efcore copied to clipboard

Throw by default for multiple owned entities set to the same instance

Open glards opened this issue 4 years ago • 6 comments

While working on a project with .NET 5.0 and EFCore 5.0.3, I found the following issue.

I am using an entity that contains twice the same owned type with different property. If I create an entity with two different instances of the owned type, it works. If I create an entity with the same instance of the owned type used twice, it breaks.

I found the issue #24541 that has the same exception but I am not sure if it is the same issue.

I was also able to reproduce the problem with the code below in a unit test off the main branch of efcore:

        private class OwnedContext : DbContext
        {
            private readonly string _databaseName;

            public OwnedContext(string databaseName)
            {
                _databaseName = databaseName;
            }

            protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
                => optionsBuilder.UseInMemoryDatabase(_databaseName);

            protected internal override void OnModelCreating(ModelBuilder builder)
            {
                builder.Entity<Owner>().HasKey(x => x.Id);
                builder.Entity<Owner>().Property(x => x.Id).ValueGeneratedOnAdd();
                builder.Entity<Owner>().OwnsOne(x => x.First);
                builder.Entity<Owner>().OwnsOne(x => x.Last);
            }
        }

        private class Owner
        {
            public long Id { get; set; }
            public Owned First { get; set; }
            public Owned Last { get; set; }
        }

        private record Owned(long A, long B, long C, long D);

        [ConditionalFact]
        public void OwnedEntity_uses_same_instance_twice()
        {
            using var context = new OwnedContext(Guid.NewGuid().ToString());

            var owned = new Owned(1,2,3,4);
            var parent = new Owner()
            {
                First = owned,
                Last = owned
            };

            context.Add(parent);
            context.SaveChanges();
        }

A workaround for the problem above is to clone the second instance e.g.:

                Last = owned with { }

The above test produces the following exception:

System.InvalidOperationException
The value of 'Owner.Last#Owned.OwnerId' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.PrepareToSave() in D:\Dev\Dotnet\efcore\src\EFCore\ChangeTracking\Internal\InternalEntityEntry.cs:line 1338
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.GetEntriesToSave(Boolean cascadeChanges) in D:\Dev\Dotnet\efcore\src\EFCore\ChangeTracking\Internal\StateManager.cs:line 955
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(StateManager stateManager, Boolean acceptAllChangesOnSuccess) in D:\Dev\Dotnet\efcore\src\EFCore\ChangeTracking\Internal\StateManager.cs:line 1168
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<>c.<SaveChanges>b__104_0(DbContext _, ValueTuple`2 t) in D:\Dev\Dotnet\efcore\src\EFCore\ChangeTracking\Internal\StateManager.cs:line 1157
   at Microsoft.EntityFrameworkCore.Storage.NonRetryingExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded) in D:\Dev\Dotnet\efcore\src\EFCore\Storage\NonRetryingExecutionStrategy.cs:line 48
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess) in D:\Dev\Dotnet\efcore\src\EFCore\ChangeTracking\Internal\StateManager.cs:line 1154
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess) in D:\Dev\Dotnet\efcore\src\EFCore\DbContext.cs:line 519
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges() in D:\Dev\Dotnet\efcore\src\EFCore\DbContext.cs:line 480
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.OwnedFixupTest.OwnedEntity_uses_same_instance_twice() in D:\Dev\Dotnet\efcore\test\EFCore.Tests\ChangeTracking\Internal\OwnedFixupTest.cs:line 73

EF Core version: 5.0.3 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET 5.0 Operating system: Windows 10 IDE: Visual Studio 2019 16.10

glards avatar Apr 08 '21 12:04 glards

Related #20474

ajcvickers avatar Apr 09 '21 16:04 ajcvickers

Note for triage: validated that the warning message is generated from the repro code given:

warn: Microsoft.EntityFrameworkCore.Update[10001]
      The same entity is being tracked as different weak entity types 'Owner.Last#Owned' and 'Owner.First#Owned'. If a property value changes, it will result in two store changes, which might not be the desired outcome.

ajcvickers avatar Apr 16 '21 14:04 ajcvickers

Is there some fix related to this bug? I had the same problem but a little bit tricky.

I divided my solution into several projects, the DbContext is separated by the owned class (in my case, Position) and throws the same error, here is the debug trace with both, the warning and the exception:

dbug: CoreEventId.StartedTracking[10806] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      Context 'TrackingDbContext' started tracking 'Position' entity with key '{SightingId: 4}'.
warn: CoreEventId.DuplicateDependentEntityTypeInstanceWarning[10001] (Microsoft.EntityFrameworkCore.Update) 
      The same entity is being tracked as different entity types 'Sighting.InitialPosition#Position' and 'Sighting.FinalPosition#Position' with defining navigations. If a property value changes, it will result in two store changes, which might not be the desired outcome.
warn: CoreEventId.DuplicateDependentEntityTypeInstanceWarning[10001] (Microsoft.EntityFrameworkCore.Update) 
      The same entity is being tracked as different entity types 'Sighting.InitialPosition#Position' and 'Sighting.FinalPosition#Position' with defining navigations. If a property value changes, it will result in two store changes, which might not be the desired outcome.
dbug: CoreEventId.StartedTracking[10806] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      Context 'TrackingDbContext' started tracking 'Position' entity with key '{SightingId: -2147482642}'.
dbug: CoreEventId.ForeignKeyChangeDetected[10803] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      The foreign key property 'SightingWhale.BehaviourId' was detected as changed from '0' to '1' for entity with key '{Id: 4}'.
dbug: CoreEventId.StartedTracking[10806] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      Context 'TrackingDbContext' started tracking 'Behaviour' entity with key '{Id: 1}'.
dbug: CoreEventId.SaveChangesStarting[10004] (Microsoft.EntityFrameworkCore.Update) 
      SaveChanges starting for 'TrackingDbContext'.
dbug: CoreEventId.DetectChangesStarting[10800] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      DetectChanges starting for 'TrackingDbContext'.
dbug: CoreEventId.DetectChangesCompleted[10801] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      DetectChanges completed for 'TrackingDbContext'.
fail: CoreEventId.SaveChangesFailed[10000] (Microsoft.EntityFrameworkCore.Update) 
      An exception occurred in the database while saving changes for context type 'Phyrenze.Track.DB.TrackingDbContext'.
      System.InvalidOperationException: The value of 'Sighting.InitialPosition#Position.SightingId' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.PrepareToSave()
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.GetEntriesToSave(Boolean cascadeChanges)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(StateManager stateManager, Boolean acceptAllChangesOnSuccess)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<>c.<SaveChanges>b__104_0(DbContext _, ValueTuple`2 t)
         at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
         at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
Exception thrown: 'System.InvalidOperationException' in Microsoft.EntityFrameworkCore.dll
An exception of type 'System.InvalidOperationException' occurred in Microsoft.EntityFrameworkCore.dll but was not handled in user code
The value of 'Sighting.InitialPosition#Position.SightingId' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.


The curious thing is if I create an entity class inside the db context project that extends from my original class works ok

    class Position : Geopositioning.Position
    {
        public Position() { }

        public Position(double latitude, double longitude, int accuracy = 0) : base(latitude, longitude, accuracy) { }
    }

with this trace

dbug: CoreEventId.StartedTracking[10806] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      Context 'TrackingDbContext' started tracking 'Position' entity with key '{SightingId: 4}'.
dbug: CoreEventId.StartedTracking[10806] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      Context 'TrackingDbContext' started tracking 'Position' entity with key '{SightingId: 4}'.
dbug: CoreEventId.ForeignKeyChangeDetected[10803] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      The foreign key property 'SightingWhale.BehaviourId' was detected as changed from '0' to '1' for entity with key '{Id: 4}'.
dbug: CoreEventId.StartedTracking[10806] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      Context 'TrackingDbContext' started tracking 'Behaviour' entity with key '{Id: 1}'.
dbug: CoreEventId.SaveChangesStarting[10004] (Microsoft.EntityFrameworkCore.Update) 
      SaveChanges starting for 'TrackingDbContext'.
dbug: CoreEventId.DetectChangesStarting[10800] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      DetectChanges starting for 'TrackingDbContext'.
dbug: CoreEventId.DetectChangesCompleted[10801] (Microsoft.EntityFrameworkCore.ChangeTracking) 
      DetectChanges completed for 'TrackingDbContext'.
warn: SqlServerEventId.SavepointsDisabledBecauseOfMARS[30004] (Microsoft.EntityFrameworkCore.Database.Transaction) 
      Savepoints are disabled because Multiple Active Result Sets (MARS) is enabled. If 'SaveChanges' fails, then the transaction cannot be automatically rolled back to a known clean state. Instead, the transaction should be rolled back by the application before retrying 'SaveChanges'. See https://go.microsoft.com/fwlink/?linkid=2149338 for more information. To identify the code which triggers this warning, call 'ConfigureWarnings(w => w.Throw(SqlServerEventId.SavepointsDisabledBecauseOfMARS))'.
dbug: RelationalEventId.BatchReadyForExecution[20700] (Microsoft.EntityFrameworkCore.Update) 
      Executing 4 update commands as a batch.
dbug: RelationalEventId.CommandCreating[20103] (Microsoft.EntityFrameworkCore.Database.Command) 
      Creating DbCommand for 'ExecuteReader'.
dbug: RelationalEventId.CommandCreated[20104] (Microsoft.EntityFrameworkCore.Database.Command) 
      Created DbCommand for 'ExecuteReader' (28ms).
dbug: 20-May-22 13:52:43.205 RelationalEventId.CommandExecuting[20100] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executing DbCommand [Parameters=[@p1='1', @p0='br' (Nullable = false) (Size = 50), @p6='4', @p2='False', @p3='False', @p4='0', @p5='0', @p13='4', @p7='1', @p8='0', @p9='False', @p10='0', @p11='False', @p12='False', @p26='4', @p14=NULL (DbType = Single), @p15='2022-05-20T13:52:42.4452449-05:00' (Nullable = true), @p16='0', @p17='2022-05-20T13:52:40.4731483-05:00', @p18=NULL (Size = 4000), @p19='0', @p20='0' (Nullable = true), @p21='16.852999999999998' (Nullable = true), @p22='99.87100000000001' (Nullable = true), @p23='0', @p24='16.852999999999998', @p25='99.87100000000001'], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      
      UPDATE [Sightings] SET [FinalDistance] = @p14, [FinalTime] = @p15, [InitialDistance] = @p16, [InitialTime] = @p17, [Notes] = @p18, [Visibility] = @p19, [FinalPositionAccuracy] = @p20, [FinalPositionLatitude] = @p21, [FinalPositionLongitude] = @p22, [InitialPositionAccuracy] = @p23, [InitialPositionLatitude] = @p24, [InitialPositionLongitude] = @p25
      WHERE [Id] = @p26;
      SELECT @@ROWCOUNT;

I could use this last for my purpose, but it's unnecessary an extra wrapping class

My ModelBuilder (all code is the same in both tests)

modelBuilder.Entity<Sighting>()
            .OwnsOne(it => it.InitialPosition,
                it =>
                {
                    it.Property(p => p.Latitude ).HasColumnName($"{nameof(Sighting.InitialPosition)}{nameof(Position.Latitude)}");
                    it.Property(p => p.Longitude).HasColumnName($"{nameof(Sighting.InitialPosition)}{nameof(Position.Longitude)}");
                    it.Property(p => p.Accuracy ).HasColumnName($"{nameof(Sighting.InitialPosition)}{nameof(Position.Accuracy)}");
                })
            .OwnsOne(it => it.FinalPosition,
                it =>
                {
                    it.Property(p => p.Latitude ).HasColumnName($"{nameof(Sighting.FinalPosition)}{nameof(Position.Latitude)}");
                    it.Property(p => p.Longitude).HasColumnName($"{nameof(Sighting.FinalPosition)}{nameof(Position.Longitude)}");
                    it.Property(p => p.Accuracy ).HasColumnName($"{nameof(Sighting.FinalPosition)}{nameof(Position.Accuracy)}");
                });

Operating system: Windows 10 IDE: Visual Studio 2022 17.1.5 SDK Version: 6.0.203

DbContextProject Microsoft.EntityFrameworkCore.Relational:6.0.5

StatupProject: Microsoft.EntityFrameworkCore:6.0.5 Microsoft.EntityFrameworkCore.Proxies:6.0.5 Microsoft.EntityFrameworkCore.SqlServer

DesktopProject TargetFramework:net6.0-windows10.0.19041.0 TargetPlatformMinVersion:10.0.17763.0

chrisdreams13 avatar May 20 '22 19:05 chrisdreams13

I'd love to see this bug fixed too. I'm running into this same issue.

TonyValenti avatar Jul 16 '22 18:07 TonyValenti

Just ran into this, took a long time to realize what was going on. Would be great to see this fixed (or allow complex types on owned entities)

bbartels avatar May 04 '25 20:05 bbartels

I would suggest at-least adding a specific exception for this to help users who run into this determine the issue

JoasE avatar Nov 27 '25 10:11 JoasE