ASP.NET Core 3.1. problem with updating collection

Viewed 342

I migrating application in ASP.NET Core from 2.1 to 3.1 and have problems with Entity Framework.

I have an entity with GlassesLenses collection:

public class GlassesContract : BaseUpdateEntity, IAggregateRoot
{
    public Guid Id { get; private set; } = Guid.NewGuid();

    private readonly List<GlassesLenses> _glassesLenses = new List<GlassesLenses>();
    public ICollection<GlassesLenses> GlassesLenses => _glassesLenses.AsReadOnly().ToList();

    public void ClearLenses()
    {
        _glassesLenses.RemoveRange(0, _glassesLenses.Count);
    }

    public void AddLens(GlassesLenses lens)
    {
        _glassesLenses.Add(lens);
    }
}

And a service for updating lenses:

public async Task<GlassesContract> ChangeSetting(GlassesContract contract)
{
    // clean lenses
    contract.ClearLenses();
    await _glassesContractRepository.UpdateAsync(contract);

    // add new lens
    contract.AddLens(new GlassesLenses());

    await _glassesContractRepository.UpdateAsync(contract);
    return contract;
}

In version 2.1 the behaviour is working fine. The collection was cleared (EF marks entity for delete) and after add new lens EF mark as new entity for insert.

In version 3.1, instead of delete and insert, EF Core only does an update.

// delete lens
UPDATE public."GlassesContracts" 
SET "AccountId" = @p0, "BalancePaymentDeposit" = @p1, ...
WHERE "Id" = @p42 AND "SysRowVer" IS NULL
RETURNING "SysRowVer";

// add new lens
UPDATE public."GlassesContracts" 
SET "AccountId" = @p0, "BalancePaymentDeposit" = @p1, ...
WHERE "Id" = @p42 AND "SysRowVer" IS NULL
RETURNING "SysRowVer";

UPDATE public."GlassesLenses" 
SET "Availability" = @p43, "Color" = @p44, ...
WHERE "Id" = @p77 AND "SysRowVer" IS NULL
RETURNING "SysRowVer";

Mapping DbContext:

// mapping relationship
    modelBuilder.Entity<GlassesContract>()
      .HasMany(_ => _.GlassesLenses)
      .WithOne(_ => _.GlassesContract)
      .OnDelete(DeleteBehavior.Cascade);
    
    modelBuilder.Entity<GlassesLenses>()
      .HasOne(_ => _.GlassesContract)
      .WithMany(_ => _.GlassesLenses);

// mapping Value objects
modelBuilder.Entity<GlassesLenses>().OwnsOne(o => o.Price).WithOwner();
modelBuilder.Entity<GlassesLenses>().OwnsOne(o => o.PriceExclVat).WithOwner();
modelBuilder.Entity<GlassesLenses>().OwnsOne(o => o.FinalPrice).WithOwner();
modelBuilder.Entity<GlassesLenses>().OwnsOne(o => o.FinalPriceExclVat).WithOwner();
modelBuilder.Entity<GlassesLenses>().OwnsOne(o => o.Discount).WithOwner();

If I mark entity with AddAsync(entity) or Remove(entity) this will generate right SQL as INSERT and DELETE.

Can you anyone explain why the new version 3.1 is not as smart as v2.1 and why SysRowVer (concurrency token) is always NULL and not updated?

Thank you so much

UPDATE

The entity tracker says 'entity GlassesLens is modified' after AddLens(..) call. I don't understand why if is added as new.

---SOLVED---

2 Answers

In the EF database context table mapping use

modelBuilder.Entity<GlassesContract >(m =>
{
        m.OwnsMany(i => i.GlassesLenses); //instead of HasMany().WithOne()
});

For more explanation on OwnsMany, check out this SO

I found solution (my knowledge mistake). Problem is breaking-changes in EF Core 3+.

We are using GUID as primary key. This is not generated value, because our application using DDD rules and entity framework changed this behaviour.

You need tell EF your ID is not generated by add DatabaseGenerated attribute

[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; private set; } = Guid.NewGuid();

or fluent in DbContext

modelBuilder
    .Entity<GlassesLenses>()
    .Property(e => e.Id)
    .ValueGeneratedNever();

God bless this post

And original is

DetectChanges honors store-generated key values

Thank you all

Related