Marco
Marco

Reputation: 23945

IsConcurrencyToken() is having no effect on Update

I am trying to implement optimistic concurrency using EF Core 2.1. I have the following entity configuration for Site:

modelBuilder.Entity<Site>(entity =>
{
    entity.ToTable("Site", "Asset");
    entity.Property(e => e.ModifiedDate).HasColumnType("datetime")
          .IsConcurrencyToken();
    //rest omitted
});

The entity is being updated inside a MediatR handler:

public class EditCommandHandler : IRequestHandler<EditCommand, ReadCommand>
{
    private readonly ApplicationContext _db;
    private readonly IMapper _mapper;

    public EditCommandHandler(ApplicationContext db, IMapper mapper)
    {
        _db = db;
        _mapper = mapper;
    }

    public async Task<ReadCommand> Handle(EditCommand command, CancellationToken cancellationToken)
    {
        /**some validation**/

        var entity = await _db.Site.SingleOrDefaultAsync(x => x.CodePath == command.CodePath);

        //map from viewModel to Site entity
        _mapper.Map(command, entity); 

        entity.ModifiedDate = DateTime.UtcNow;
        _db.Site.Update(entity);

        //should throw exception here
        await _db.SaveChangesAsync(cancellationToken);

        /**load navigation Properties and return viewmodel. Logic omitted*/
        return site;
    }

I would expect, that if I try to update the entity, with a different ModifiedDate, then the one, that is being retrieved from the database, that a DbUpdateConcurrencyException is being thrown, but it isn't. The changes are successfully being written to the database.

Please note, that I am forced to use the ModifiedDate property and am not able to implement a RowVersion column on the database.

Why is the concurrency check not happening? What did I do wrong here?

Upvotes: 0

Views: 753

Answers (1)

Marco
Marco

Reputation: 23945

I solved it, although I have no idea, why it is working now. If somebody has an explanation for this, I'm all ears.

The first thing I did, after some more hours of try & error, was change to a RowVersion approach, although I could not merge that code in using it. It behaved the same - the rowversion was seemingly ignored.

This brought me on the track, to explicitly set the original values after the mapping has happened:

_mapper.Map(command, entity);
//for some reason that helps! ha!
_db.Entry(entity).OriginalValues["ModifiedDate"] = command.ModifiedDate;

entity.ModifiedDate = DateTime.UtcNow;
try
{
    await _db.SaveChangesAsync(cancellationToken);
}

Now this throws an exception, if either the RowVersion or the attribute marked as the concurrencytoken does not add up.

I have checked both values, before and after the mapping, and they original ModifiedDate did not change, so I have no idea, why setting it to the commands ModifiedDate brought the effect it now has.

I'll now move on to write some unit tests, making sure, nobody else accidently breaks this.

Upvotes: 1

Related Questions