Issue with owned collection where owner has string primary key
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- PR merge metrics
- PR metrics pending
Description
### Bug description
Having issues with mapping a owner and owned entity collection where owner has `string` primary key.
When inserting a new owner and adding owned entries everything works as expeced. Owner & owned entities are added.
But when loading the entity from a new context (or even modifying the owned collection in the same context, but after calling SaveChanges) EF core either gives an error when adding an owned entity without setting OwnerId (ie it has `null` value) or if OwnerId is set to correct id it won't detect the change and persist it (IE reloading owner from DB, it won't have any owned entties).
Using HasMany with same owned entity configuration works fine.
Using an owner that has a non string key e.g. int also works fine.
Although I am using Postgresql database, I don't think it should matter.
### Your code
```csharp
using Microsoft.EntityFrameworkCore;
var connectionString = args[0];
var contextOptions = new DbContextOptionsBuilder()
.UseNpgsql(connectionString)
.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
using (var dbContext = new AppContext(contextOptions.Options))
{
dbContext.Database.EnsureCreated();
// works fine when creating new owner and also adding owned entities no error that OwnerId is null
dbContext.OwnerOwnsMany.Add(new OwnerOwnsMany
{
Id = "owner_with_owned",
Owned = new List { new OwnedOwnsMany { ForeignId = 1 } },
});
// does not work when adding owned entities after the fact (see below)
dbContext.OwnerOwnsMany.Add(new OwnerOwnsMany { Id = "owner_id", });
// works fine when owned entites have been defined as HasMany
dbContext.OwnersHasMany.Add(new OwnerHasMany { Id = "owner_id", });
// works fine when owner has non string primary key
dbContext.OwnerOwnsManyInt.Add(new OwnerOwnsManyIntId { Id = 1, });
await dbContext.SaveChangesAsync();
}
using (var dbContext = new AppContext(contextOptions.Options))
{
// When modifying existing OwnsMany then:
var ownerOwnsMany = await dbContext.OwnerOwnsMany.FindAsync("owner_id");
// Error "Unable to track an entity of type 'Owned' because its primary key property 'OwnerId' is null
//ownerOwnsMany.Owned.Add(new OwnedOwnsMany { ForeignId = 1 });
// Does not detect any changes, if queried below its as if this newer happened
ownerOwnsMany.Owned.Add(new OwnedOwnsMany { OwnerId = ownerOwnsMany.Id, ForeignId = 1 });
// HasMany works fine no error and owned entry added as expected
var ownerHasMany = await dbContext.OwnersHasMany.FindAsync("owner_id");
ownerHasMany.Owned.Add(new OwnedHasMany { ForeignId = 1 });
// OwsnMany with non string id works fine no error
var ownerOwnsManyInt = await dbContext.OwnerOwnsManyInt.FindAsync(1);
ownerOwnsManyInt.Owned.Add(new OwnedOwnsManyIntId { ForeignId = 1 });
await dbContext.SaveChangesAsync();
}
using (var dbContext = new AppContext(contextOptions.Options))
{
var ownerOwnsMany = await dbContext.OwnerOwnsMany.FindAsync("owner_id");
if (ownerOwnsMany!.Owned.Count == 0) Console.WriteLine("OwnerOwnsMany should own at least one thing!");
else Console.WriteLine("OwnerOwnsMany has owned entities");
var ownerOwnsManyWithOwned = await dbContext.OwnerOwnsMany.FindAsync("owner_with_owned");
if (ownerOwnsManyWithOwned!.Owned.Count == 0) Console.WriteLine("OwnerOwnsMany should own at least one thing!");
else Console.WriteLine("OwnerOwnsMany has owned entities");
var ownerHasMany = await dbContext.OwnersHasMany.FindAsync("owner_id");
if (ownerHasMany!.Owned.Count == 0) Console.WriteLine("OwnerHasMany should own at least one thing!");
else Console.WriteLine("OwnerHasMany has owned entities");
var ownerOwnsManyInt = await dbContext.OwnerOwnsManyInt.FindAsync(1);
if (ownerOwnsManyInt!.Owned.Count == 0) Console.WriteLine("OwnerOwnsManyIntId should own at least one thing!");
else Console.WriteLine("OwnerOwnsManyIntId has owned entities");
}
public class AppContext : DbContext
{
public AppContext(DbContextOptions options) : base(options) { }
public DbSet OwnerOwnsMany => Set();
public DbSet OwnerOwnsManyInt => Set();
public DbSet OwnersHasMany => Set();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.HasKey(x => x.Id);
modelBuilder.Entity()
.OwnsMany(x => x.Owned, ob =>
{
ob.WithOwner().HasForeignKey(x => x.OwnerId);
ob.HasKey(x => new { x.OwnerId, x.ForeignId });
});
modelBuilder.Entity()
.HasKey(x => x.Id);
modelBuilder.Entity()
.OwnsMany(x => x.Owned, ob =>
{
ob.WithOwner().HasForeignKey(x => x.OwnerId);
ob.HasKey(x => new { x.OwnerId, x.ForeignId });
});
modelBuilder.Entity()
.HasKey(x => x.Id);
modelBuilder.Entity()
.HasMany(x => x.Owned)
.WithOne().HasForeignKey(x => x.OwnerId);
modelBuilder.Entity()
.Navigation(x => x.Owned).AutoInclude();
modelBuilder.Entity()
.HasKey(x => new { x.OwnerId, x.ForeignId });
}
}
public class OwnerOwnsMany
{
public required string Id { get; set; }
public List Owned { get; set; } = [];
}
public class OwnedOwnsMany
{
public string OwnerId { get; set; } = default!;
public long ForeignId { get; set; }
}
public class OwnerHasMany
{
public required string Id { get; set; }
public List Owned { get; set; } = [];
}
public class OwnedHasMany
{
public string OwnerId { get; set; } = default!;
public long ForeignId { get; set; }
}
public class OwnerOwnsManyIntId
{
public required int Id { get; set; }
public List Owned { get; set; } = [];
}
public class OwnedOwnsManyIntId
{
public int OwnerId { get; set; } = default!;
public long ForeignId { get; set; }
}
```
### Stack traces
```text
Error & stack trace when `string` OwnerId was not set during update
> Unable to track an entity of type 'OwnedOwnsMany' because its primary key property 'OwnerId' is null.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NullableKeyIdentityMap`1.Add(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey, Nullable`1 fallbackState)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.NavigationCollectionChanged(InternalEntityEntry entry, INavigationBase navigationBase, IEnumerable`1 added, IEnumerable`1 removed)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.NavigationCollectionChanged(InternalEntityEntry entry, INavigationBase navigationBase, IEnumerable`1 added, IEnumerable`1 removed)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectNavigationChange(InternalEntityEntry entry, INavigationBase navigationBase)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.LocalDetectChanges(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager)
at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.DetectChanges()
at Microsoft.EntityFrameworkCore.DbContext.TryDetectChanges()
at Microsoft.EntityFrameworkCore.DbContext.d__63.MoveNext()
at Program.<$>d__0.MoveNext() in D:\experimental_projects\EFCoreBugWithOwnsManyAndStringPrimaryKey\App\Program.cs:line 51
```
### Verbose output
```text
```
### EF Core version
9.0.4
### Database provider
Npgsql.EntityFrameworkCore.PostgreSQL
### Target framework
.NET 9
### Operating system
Windows 11
### IDE
Visual Studio 2022 17.14
Contributor guide
Assessment
This issue has not been assessed yet.