EF Core running query's I did not call

hwamh0ep  于 2023-02-21  发布在  其他
关注(0)|答案(1)|浏览(127)

I have the following two models within my Blazor Server project:

Vergadering:

public class Vergadering
{
    [Key]
    public int Id { get; set; }
    public string Naam { get; set; }
    public DateTime DatumTijd { get; set; }
    public ICollection<Bestuurslid> Aanwezigen { get; set; }
    public string? Notulen { get; set; }
    public ICollection<Vergadering>? HoofdVergadering { get; set; }
    public ICollection<Vergadering>? GekoppeldeVergaderingen { get; set; }
    public ICollection<Bestand>? Bestanden { get; set; }
    public string? UserLastEditId { get; set; }
    public IdentityUser? UserLastEdit { get; set; }
    public DateTime? LastEdit { get; set; }
    public ICollection<VergaderingAgendaItem>? vergaderingAgendaItems { get; set; }
}

VergaderingAgendaItem:

public class VergaderingAgendaItem
{
    public int Id { get; set; }
    public string Omschrijving { get; set; }
    public bool Afgerond { get; set; }
    public int? ParentId { get; set; }
    public VergaderingAgendaItem? Parent { get; set; }
    public int VergaderingId { get; set; }
    public Vergadering Vergadering { get; set; }
    public string? UserAangedragenId { get; set; }
    public IdentityUser? UserAangedragen { get; set; }

}

This results in three tables:

Vergaderingen
VergaderingAgendaItems
VergaderingVergadering

In my repository I have the following update method:

public async Task ChangeAfgerondStatusAsync(VergaderingAgendaItem item)
{
    using (var _db = _factory.CreateDbContext())
    {
        _db.VergaderingAgendaItems.Update(item);
        await _db.SaveChangesAsync();
    }
}

Whenever the Vergadering does not have a GekoppeldeVergadering this update method does not create any problem.

But whenever the Vergadering does have a GekoppeldeVergadering and I update a VergaderingAgendaItem of that Vergadering I get this error:
An error occurred while saving the entity changes. See the inner exception for details.

Looking at the command prompt that opens up while running the project I saw the following query and error.

Queries:

Error:
An exception occurred in the database while saving changes for context type 'AVA_ZICHT.Data.ApplicationDbContext'.

Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.

Microsoft.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'PK_VergaderingVergadering'. Cannot insert duplicate key in object 'dbo.VergaderingVergadering'. The duplicate key value is (4, 3).

How is it that EF Core tries to update the GekoppeldeVergadering in VergaderingVergadering table. My method states VergaderingAgendaItem.Update() ?

jei2mxaa

jei2mxaa1#

When handed a detached entity and told to Update it, EF will consider any associated entities as well. Since those references aren't tracked by the DbContext, the context will see those entities as new items to be inserted. This can result in duplicate key exceptions (as you are seeing) or inserting duplicate data with new PKs if those keys are set up as Identity columns.

One way to get around this issue is to use Automapper configured to just update the columns you expect to change:

public async Task ChangeAfgerondStatusAsync(VergaderingAgendaItem item)
{
    using (var _db = _factory.CreateDbContext())
    {
        var existingItem = _db.VergaderingAgendaItems.Single(x => x.Id == item.Id);
        Mapper.Map(item, existingItem);
        await _db.SaveChangesAsync();
    }
}

Alternatively this can be done manually by copying values from item to existingItem. existingItem is tracked entity so once it's updated, just call SaveChanges. The advantage of this over Update is that the resulting UPDATE SQL statement will only be for any columns that have actually changed, and it won't execute an UPDATE if nothing has actually changed.

This assumes we only want to copy fields from that entity, and none of the child/related entities. If you want to alter the collections/associations then you will need to eager load them and handle these separately. For instance changing the UserLastEdit reference, this is likely something you would want to eager-load so that it can be updated with the current User record.

My general advice is to avoid working with detached entities for concerns like this and instead use POCO view models. The trouble with using detached entities is that these are often incomplete representations of entity state, at worst, something deserialized from view state and cast into an Entity object. View Models can also be scaled down to just the data your client needs and what data is allowed to change. When it gets back to the server there is no confusion about what it is vs. what it pretends to be. Another consideration of applying updates which is important in multi-user systems is detecting stale data. Writing updates like this applies a "last in wins" approach where you should ideally check that the current DB data state concurrency token matches the token/version at the time that this user's original version was read. The attraction of using detached entities is the thought of avoiding a round-trip to the DB when performing an update, but in all honesty you should justify a round trip to ensure that the record is actually valid, the user actually can update that record, and the record hasn't been updated by someone else in the time this user was editing it.

相关问题