Skip to content

OwnsMany when updating owner entity doesn't perform insert statement if the owned entity contains a Guid property #30558

@Jorg10

Description

@Jorg10

Steps to reproduce the issue:

  • Define a owned entity with only a String property.
  • Define a owned entity with only a Guid property.
  • Define a owner entity with two lists of the two owned entities defined before.
  • Map both owned entities to a table with OwnsMany in the owner entity.
  • For both owned entities don't add a surrogate key but define the key as the combination of the foreign key and their only property.
  • Insert a owner entity with values in both owned entities: when inserting, EF correctly generates insert statement for the owned entities
  • Update the owner entity created before changing the values of the owned entities: when updating, EF generates the insert statement only for the owned entity with a String property and not for the owned entity with a Guid property (however it deletes the owned entities as it recognizes they are changed).

See attached project or the console application code below.

docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=testPassword1!" -p 1433:1433 --name test-db --hostname test-db -d mcr.microsoft.com/mssql/server:2022-latest
using Microsoft.EntityFrameworkCore;

var id = Guid.NewGuid();

using (var context = new TestDbContext())
{
    var owners = await context.OwnerEntities.ToListAsync();

    foreach (var owner in owners)
    {
        context.OwnerEntities.Remove(owner);

        context.SaveChanges();
    }
}

using (var context = new TestDbContext())
{
    var owner = new OwnerEntity(id);

    owner.SetOwnedEntitiesWithString(new OwnedEntityWithString("Hello"), new OwnedEntityWithString("World"));
    owner.SetOwnedEntitiesWithGuid(new OwnedEntityWithGuid(Guid.NewGuid()), new OwnedEntityWithGuid(Guid.NewGuid()));

    context.OwnerEntities.Add(owner);

    context.SaveChanges();
}

using (var context = new TestDbContext())
{
    var owner = await context.OwnerEntities.FindAsync(id) ?? throw new Exception();

    Console.WriteLine($"OwnedEntitiesWithString after insert: {owner.OwnedEntitiesWithString.Count}"); //Should be 2. Is 2.

    Console.WriteLine($"OwnedEntitiesWithGuid after insert: {owner.OwnedEntitiesWithGuid.Count}"); //Should be 2. Is 2.

    owner.SetOwnedEntitiesWithString(new OwnedEntityWithString("Ciao"), new OwnedEntityWithString("Mondo"));
    owner.SetOwnedEntitiesWithGuid(new OwnedEntityWithGuid(Guid.NewGuid()), new OwnedEntityWithGuid(Guid.NewGuid()));

    context.SaveChanges();
}

using (var context = new TestDbContext())
{
    var owner = await context.OwnerEntities.FindAsync(id) ?? throw new Exception();

    Console.WriteLine($"OwnedEntitiesWithString after update: {owner.OwnedEntitiesWithString.Count}"); //Should be 2. Is 2.

    Console.WriteLine($"OwnedEntitiesWithGuid after update: {owner.OwnedEntitiesWithGuid.Count}"); //Should be 2. Is 0.
}


public class OwnerEntity
{
    private readonly List<OwnedEntityWithString> ownedEntitiesWithString = new();

    private readonly List<OwnedEntityWithGuid> ownedEntitiesWithGuid = new();

    public Guid Id { get; private set; }

    public OwnerEntity(Guid id)
    {
        Id = id;
    }

    public IReadOnlyList<OwnedEntityWithString> OwnedEntitiesWithString => ownedEntitiesWithString.AsReadOnly();

    public IReadOnlyList<OwnedEntityWithGuid> OwnedEntitiesWithGuid => ownedEntitiesWithGuid.AsReadOnly();

    public void SetOwnedEntitiesWithString(params OwnedEntityWithString[] ownedEntities)
    {
        ownedEntitiesWithString.Clear();

        ownedEntitiesWithString.AddRange(ownedEntities);
    }

    public void SetOwnedEntitiesWithGuid(params OwnedEntityWithGuid[] ownedEntities)
    {
        ownedEntitiesWithGuid.Clear();

        ownedEntitiesWithGuid.AddRange(ownedEntities);
    }
}

public class OwnedEntityWithString
{
    public string Value { get; private set; } = string.Empty;

    public OwnedEntityWithString(string value)
    {
        Value = value;
    }
}

public class OwnedEntityWithGuid
{
    public Guid Value { get; private set; }

    public OwnedEntityWithGuid(Guid value)
    {
        Value = value;
    }
}

public class TestDbContext : DbContext
{
    public DbSet<OwnerEntity> OwnerEntities => Set<OwnerEntity>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);

        optionsBuilder.UseSqlServer(
            "Server=localhost;Database=test-db;User Id=sa;Password=testPassword1!;TrustServerCertificate=true;MultipleActiveResultSets=true");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<OwnerEntity>()
            .ToTable("OwnerEntities")
            .Property(x => x.Id)
            .ValueGeneratedNever();

        modelBuilder.Entity<OwnerEntity>()
            .HasKey(x => x.Id);

        modelBuilder.Entity<OwnerEntity>()
                    .OwnsMany(x => x.OwnedEntitiesWithString, o =>
            {
                o.ToTable("OwnedEntitiesWithString");

                o.WithOwner()
                    .HasForeignKey("OwnerEntityId");

                o.HasKey("OwnerEntityId", nameof(OwnedEntityWithString.Value));

                o.Property(x => x.Value)
                    .HasMaxLength(50);
            })
            .OwnsMany(x => x.OwnedEntitiesWithGuid, o =>
            {
                o.ToTable("OwnedEntitiesWithGuid");

                o.WithOwner()
                    .HasForeignKey("OwnerEntityId");

                o.HasKey("OwnerEntityId", nameof(OwnedEntityWithGuid.Value));
            });

        modelBuilder.Entity<OwnerEntity>()
            .Metadata
            .FindNavigation(nameof(OwnerEntity.OwnedEntitiesWithString))!
            .SetPropertyAccessMode(PropertyAccessMode.Field);

        modelBuilder.Entity<OwnerEntity>()
            .Metadata
            .FindNavigation(nameof(OwnerEntity.OwnedEntitiesWithGuid))!
            .SetPropertyAccessMode(PropertyAccessMode.Field);

    }
}

EF Core version: 7.0.4
Database provider: (e.g. Microsoft.EntityFrameworkCore.SqlServer)
Target framework: (e.g. .NET 7.0)
Operating system: Windows 10 22H2
IDE: (e.g. Visual Studio 2022 17.5.2)

TestEfCore.zip

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions