Search code examples
c#entity-framework-coreowned-types

Owned types collection is never updated in EF Core


I have an aggregate defined like this:

public class Product {
    public int LocalId { get; private set; }
    public Something Something { get; private set; }
    public ICollection<Price> Prices { get; private set; }
}

public class Something {
    public string Name { get; set; }
}

public class Price {
    public int Type { get; set; }
    public decimal Value { get; set; }
}

And a schema defined like this:

private void DefineProduct(ModelBuilder builder) =>
    builder
        .Entity<Product>(builder =>
        {
            builder.HasKey(p => p.LocalId);
            builder
                .OwnsOne(p => p.Something, smth =>
                {
                    smth.ToTable("somethings");
                })
                .OwnsMany(p => p.Prices, pp =>
                {
                    pp.ToTable("prices");
                });
        });

When a price change is requested, I do this (inside the product method not included here for brevity):

Prices.First(p => p.Type == type).Value = newValue;

And then I try to save the product like this:

public async Task UpdateProperties(Product product, IEnumerable<object> props)
{
    _context.Attach(product);
    _context.Update(product);

    foreach (var prop in props)
    {
        _context.Update(prop);
    }

    try
    {
        await _context.SaveChangesAsync();
    } 
    catch (Exception ex)
    {
        Console.WriteLine("Who the hell allowed such a bug to go into a production release?");
    }
}

Now I should mention that the product comes in from an initial query whose results were not tracked (via AsNoTracking() call), that's why I'm calling the Attach method in the first line of the method body. The problem is that I'm hitting that catch statement with an exception message saying:

Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions."}

The thing is that I'm not updating the same product anywhere else and that's the only place touching it. Also I use AsNoTracking as the default. If I comment out the line with _context.Update(prop);, then there's no exception raised, but the price is not being updated. Also, if I don't update that prices collection but the Something property, everything goes well. What. The. Hell.


Solution

  • EF Core documentation for Collections of owned types explicitly states that you have to define the owned entity PK (as opposed to OwnsOne where the shadow FK is normally used as PK).

    Hence you need to either define its own PK (like Id you did), or composite PK - for instance, if Price.Type is unique inside the owner, then you can use something like

    pp.HasKey("LocalId", "Type");
    

    and avoid the additional Id column.