Social Icons

twitter google plus linkedin rss feed

Pages

22.4.19

Concurrency Errors and Value Objects in Entity Framework 2.x

We have been using value objects in our databases for a couple of months and after we passed the first hurdles with entity framework we have noticed improvements in speed and a significant decrease in the Includes wich is all what we wanted to get... but suddenly...

In one of the microservices we noticed we were getting concurrency exceptions every time we updated a value object. And it wasn't a complicated nested value object, it was a value object made of two guids and two strings.

I spent a good afternoon trying to figure out why were we getting the error and could not see anything wrong in the code... and I thought... it's entity framework again!

I asked for help to the rest of the team and they found this thread Changes on Owned Entites Properties causes a concurrency conflict on same dbContext where they propose a workaround. We tried and it didn't work but we thought it was going in the right direction so we debug it and change it a bit and voilà the rowversion column was being updated again both in SQL and in my backend!

The modified code is this:
private void ConcurrencyFix()
{
    var changedEntriesWithVos = ChangeTracker.Entries().Where(e =>
        e.State == EntityState.Unchanged
        && e.References.Any(r =>
            r.TargetEntry != null
            && (r.TargetEntry.State == EntityState.Modified || r.TargetEntry.State == EntityState.Added)
            && r.TargetEntry.Metadata.IsOwned()
            && e.Metadata.Relational().TableName == r.TargetEntry.Metadata.Relational().TableName)).ToArray();

    foreach (var entry in changedEntriesWithVos)
        entry.State = EntityState.Modified;
}

And we have placed it in our SaveChanges methods in our base context, from which all of our contexts are inheriting so we are sure this code is always being executed when we save.
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
    ConcurrencyFix();
    return base.SaveChanges(acceptAllChangesOnSuccess);
}

public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
    ConcurrencyFix();
    return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}

Have fun everyone!

No comments:

Post a Comment