如何使用 ForNpgsqlUseXminAsConcurrencyToken 创建的 EF Core 并发令牌

How to use EF Core concurrency token created by ForNpgsqlUseXminAsConcurrencyToken

我发现 npgsql 提供程序 extension 为 entity framework 核心实体设置并发令牌,它应该做这样的事情:

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

如果我理解得很好,它会在实体上创建阴影 属性。

例如,

如何使用此 属性 跟踪 ASP.NET Core 中的并发更新(更多用户尝试更新同一实体)?我是否应该尝试将 xmin 列映射到正常 属性 并将其放入隐藏的输入标签,如 asp.net core documentation 中所示?或者有别的办法吗?

跟踪由 Entity Framework 自动为您完成。

基本上,它是这样的:

  1. 用户 A 使用 ID 1 加载 MyEntity。
  2. 用户 B 使用 ID 1 加载 MyEntity。
  3. 用户A保存了对id为1的MyEntity的修改。xmin列由PostgreSQL自动修改。
  4. 用户 B 保存了对 ID 为 1 的 MyEntity 的修改。entity Framework 引发了 OptimisticConcurrencyException,因为 xmin 的值在用户更改数据和他尝试更新数据之间发生了变化数据。

从技术上讲,在此示例中,xmin 值用于更新语句期间的 where 子句中。因为xmin的值变了,所以UPDATE查询影响的行数是0,而不是1。

通过与 Olivier MATROT 的讨论,我意识到如何做我需要做的事情。

该解决方案并不理想,因为它与提供程序捆绑在一起(SQL 服务器提供程序需要 byte[] 作为并发令牌 属性),但按预期工作:

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

在上下文中(如果使用迁移,属性 需要从迁移代码中删除以消除列创建尝试)

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    // ...

    builder.Entity<MyEntity>()
        .Property(e => e.ConcurrencyStamp)
            .ForNpgsqlHasColumnName("xmin")
            .ForNpgsqlHasColumnType("xid")
            .ValueGeneratedOnAddOrUpdate()
            .IsConcurrencyToken();
}

编辑视图

@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>

和默认的脚手架操作(只是为了完成示例)

[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);
}

因此解决方案是使 xmin 值作为实体可访问 属性。