Search code examples
c#asp.net-coreconcurrencyentity-framework-corenpgsql

How to use EF Core concurrency token created by ForNpgsqlUseXminAsConcurrencyToken


I have found npgsql provider extension to set up concurrency token for entity framework core entity, which should do something like this:

modelBuilder.Entity<MyEntity>(b =>
{
    b.Property<uint>("xmin")
        .HasColumnType("xid")
        .ValueGeneratedOnAddOrUpdate()
        .IsConcurrencyToken();
});

If I understand it well, it creates shadow property on entity.

How can I use this property to track concurrent updates (more users try to update the same entity) in ASP.NET Core, for example? Should I try to to map xmin column to normal property and put it to hidden input tag as it is showed in asp.net core documentation? Or is there another way?


Solution

  • Discussing with Olivier MATROT I realized how to do what I need.

    The solution is not ideal because it is tied up with provider (SQL server provider needs byte[] as concurrency token property), but works as expected:

    public class MyEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public uint ConcurrencyStamp { get; set; }
    }
    

    In the context (If migrations are used, property need to be removed from migration code to eliminate column creation attempt)

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    
        // ...
    
        builder.Entity<MyEntity>()
            .Property(e => e.ConcurrencyStamp)
                .ForNpgsqlHasColumnName("xmin")
                .ForNpgsqlHasColumnType("xid")
                .ValueGeneratedOnAddOrUpdate()
                .IsConcurrencyToken();
    }
    

    Edit view

    @model Namespace.MyEntity
    
    <form asp-action="Edit">
        <div class="form-horizontal">
            <h4>Person</h4>
            <hr />
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    
            <input type="hidden" asp-for="Id" />
            <input type="hidden" asp-for="ConcurrencyStamp" />
    
            <div class="form-group">
                <label asp-for="Name" class="col-md-2 control-label"></label>
                <div class="col-md-10">
                    <input asp-for="Name" class="form-control" />
                    <span asp-validation-for="Name" class="text-danger"></span>
                </div>
            </div>
    
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Save" class="btn btn-default" />
                </div>
            </div>
    
        </div>
    </form>
    

    and default scaffolded action (just to complete the example)

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(int id, [Bind("Id,Name,ConcurrencyStamp")] MyEntity model)
    {
        if (id != model.Id)
        {
            return NotFound();
        }
    
        if (ModelState.IsValid)
        {
            try
            {
                _context.Update(model);
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!MyEntityExists(model.Id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
            return RedirectToAction("Index");
        }
        return View(model);
    }
    

    So the solution is to make xmin value accessible as the entity property.