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.