Model with multiple nested owned entities and properties of same types within a single entity throws InvalidOperationException
My model nests 2 levels of owned entities and has multiple properties of the same type contained in a single entity.
Structure:
FooStatisticsFooStatistics (Owned)PercentileRangePercentile25En75 (Owned)doubleLowdoubleHigh
PercentileRangePercentile10En90 (Owned)doubleLowdoubleHigh
doubleMedian
StatisticsBarStatistics (Owned)PercentileRangePercentile25En75 (Owned)doubleLowdoubleHigh
PercentileRangePercentile10En90 (Owned)doubleLowdoubleHigh
doubleMedian
When I create an instance of Foo and add this to the DBContext and call the SaveChanged method the following error is thrown.
Error
System.InvalidOperationException: 'Cannot save instance of 'Foo.BarStatistics#Statistics.Percentile25En75#PercentileRange' because it is an owned entity without any reference to its owner. Owned entities can only be saved as part of an aggregate also including the owner entity.
I am saving the aggregate root (Foo) therefore I do not understand why the error is thown. If I were to save the Statistics without the Foo I would understand that such an error would be thrown. Perhaps I made a mistake in the configuration, but I can't detect any.
Stacktrace
Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.PrepareToSave() Line 1572 C#
Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.GetEntriesToSave(bool cascadeChanges) Line 1047 C#
Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager stateManager, bool acceptAllChangesOnSuccess) Line 1256 C#
Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges.AnonymousMethod__107_0(Microsoft.EntityFrameworkCore.DbContext _, (Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager StateManager, bool AcceptAllChangesOnSuccess) t) Line 1246 C#
Microsoft.EntityFrameworkCore.SqlServer.dll!Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute<(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager, bool), int>((Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager, bool) state, System.Func<Microsoft.EntityFrameworkCore.DbContext, (Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager, bool), int> operation, System.Func<Microsoft.EntityFrameworkCore.DbContext, (Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager, bool), Microsoft.EntityFrameworkCore.Storage.ExecutionResult<int>> verifySucceeded) Line 57 C#
Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(bool acceptAllChangesOnSuccess) Line 1242 C#
Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.DbContext.SaveChanges(bool acceptAllChangesOnSuccess) Line 631 C#
Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.DbContext.SaveChanges() Line 582 C#
EFCoreOwnedEntitiesError.dll!EFCoreOwnedEntitiesError.Program.TestFoo() Line 39 C#
EFCoreOwnedEntitiesError.dll!EFCoreOwnedEntitiesError.Program.Main(string[] args) Line 9 C#
Entity configuration
internal sealed class FooEntityTypeConfiguration : IEntityTypeConfiguration<Foo>
{
public void Configure(EntityTypeBuilder<Foo> builder)
{
builder.OwnsOne(foo => foo.FooStatistics, statisticsBuilder =>
{
statisticsBuilder.OwnsOne(statistics => statistics.Percentile25En75);
statisticsBuilder.OwnsOne(statistics => statistics.Percentile10En90);
});
builder.OwnsOne(foo => foo.BarStatistics, statisticsBuilder =>
{
statisticsBuilder.OwnsOne(statistics => statistics.Percentile25En75);
statisticsBuilder.OwnsOne(statistics => statistics.Percentile10En90);
});
}
}
Code
The following sample repository can be pulled to reproduce the issue. https://github.com/royvandertuuk/EFCoreIssueOwnedEntities/tree/main
Version information
EF Core version: Microsoft.EntityFrameworkCore.SqlServer (7.0.10) Target framework: net7.0 Operating system: Microsoft Windows 11 Pro IDE: Visual Studio 2022 Version 17.7.1
I noticed a shortcomming of Owned Entity Types on page https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities.
namely: instances of owned entity types cannot be shared by multiple owners (this is a well-known scenario for value objects that cannot be implemented using owned entity types)..
The sample project https://github.com/royvandertuuk/EFCoreIssueOwnedEntities reused the same object instance for multiple owners for the sake of simplicity. When I changed the code so that all the owners have there own instance of the object, the error dissapeared. After checking the actual project where the error originally occurred, I noticed a bug that accidentally reused instances of owned entities. Changing the incorrect code solved the issue in this project as well.
The error Cannot save instance of '...' because it is an owned entity without any reference to its owner. Owned entities can only be saved as part of an aggregate also including the owner entity. is misleading, because it suggests that the owned entity itself is being saved in stead of the aggregate, and this was not the case. An error like Owned entity instances cannot be used by multiple owners would make a lot more sense and would have saved me a lot of time while debugging this issue.
It would be great if the error message can be changed in future versions to avoid confusion for other developers.
Note from triage: add to the existing message to indicate that this might be because of sharing an entity instance.
@royvandertuuk So if I'm understanding correctly, I can't use the same Owned type in multiple entity definitions? e.g.,
record Foo
{
public required string Name { get; init; }
}
record FirstEntity
{
public Guid Id { get; init; }
public required Foo Foo { get; init; }
}
record SecondEntity
{
public Guid Id { get; init; }
public required Foo Foo { get; init; }
}
That should work fine as long as the FirstEntity and SecondEntity object instances do not reference the same Foo object instance.
If you have 2 Foo object instances (even if they have the same data) and the FirstEntity and SecondEntity objects each reference their own instance of Foo, it should work fine.