Fanda
Fanda

Reputation: 3786

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?

Upvotes: 7

Views: 5836

Answers (2)

Fanda
Fanda

Reputation: 3786

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.

Upvotes: 7

anon
anon

Reputation:

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.

Upvotes: 0

Related Questions