7

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?

Askolein
  • 3,250
  • 3
  • 28
  • 40
Fanda
  • 3,760
  • 5
  • 37
  • 56

2 Answers2

7

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.

Fanda
  • 3,760
  • 5
  • 37
  • 56
  • Yes the concurrency token must be mapped to an entity property. So this is the database engine that manages this token. You could learn more about that in an [old question of mine](http://stackoverflow.com/questions/11257826/entity-framework-postgresql-optimistic-concurrency-with-hidden-xmin-column). –  Apr 28 '17 at 07:22
0

The tracking is done automatically for you by Entity Framework .

Basically, it goes like this :

  1. User A load MyEntity with id 1.
  2. User B load MyEntity with id 1.
  3. User A saves a modification for MyEntity with id 1. The xmin column is automatically modified by PostgreSQL.
  4. User B saves a modification for MyEntity with id 1. An OptimisticConcurrencyException is raised by entity Framework because the value of xmin has changed between the moment the user has red the data and the moment he tries to update the data.

Technically, in this sample, the xmin value is used in a where clause during the update statment. Because the value of xmin has changed, the number of row affected by the UPDATE query is 0 instead of 1.

  • But then I need to remember somehow the xmin value from the entity load time, right? How, if I don't send it back by saving form? If I load the entity on server side again, it will have new value already... – Fanda Apr 27 '17 at 13:41
  • This is done automatically for you by the code you included in your question. You could try it simply by loading the entity, modify the corresponding record in the PostgreSQL table with pgAdmin, modify you entity and save the changes. –  Apr 27 '17 at 14:00
  • Yes, I now, but used in asp.net, you need to send it to client and then post it back, compose entity again and then save. So, it is detached entity (without xmin value), if you don't send xmin explicitly by the client. And generating view, you need to access xmin value to fill some hidden field to post the value back on form save. – Fanda Apr 27 '17 at 19:20