dotnet / dotnet/efcore

Issue with owned collection where owner has string primary key

Open
#36,480 1 comment 0 reactions 1 assignee Claimed by @AndriySvyryd View on GitHub
area-change-tracking area-owned-entities customer-reported
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.