尽管更新冲突,但EF不会抛出DbUpdateConcurrencyException

时间:2017-06-17 22:31:13

标签: c# entity-framework asp.net-web-api

我在带有Web客户端的Web API服务器上使用EF 6.x(代码优先),我需要实现并发处理。问题是我甚至无法获得EF来生成异常。

我发现的大多数示例似乎都没有使用“分离实体”,其中DTO被发送到Web客户端,在那里它被更新,然后在以后保存回服务器(这是我的方案)。

假设我有公司记录:

public class Company 
{
    int CompanyId { get; set; }
    string CompanyName { get; set; }

    [Timestamp]
    public byte[] RowVersion { get; set; }
}

1)用户A提取公司Id = 0,RowVersion为0x0000000000002B0A

2)我运行UPDATE Company SET CompanyName = 'Acme Changed, Inc.' WHERE CompanyId = 0来模拟其他用户的更改。 RowVersion更改为0x0000000000002B0B

3)用户A将CompanyName更改为“Acme,The Great!”并单击“保存”(从浏览器)

4)公司DTO通过CompanyName =“Acme,The Great!”到达Web API服务器。和旧RowVersion = 0x0000000000002B0A

5)我从数据库中检索公司记录,更新并保存:

public void UpdateCompany(Company updatedCompany)
{
    var dbCompany = Context.Companies.SingleOrDefault(r => r.CompanyId == updatedCompany.CompanyId);
    dbCompany.CompanyName = updatedCompany.CompanyName;
    dbCompany.RowVersion = updatedCompany.RowVersion;  // Set RowVersion to the passed in original RowVersion 0x0000000000002B0A

    try
    {
        DbContext.SaveChanges();
    }
    catch (DbUpdateConcurrencyException ex)
    {
        // Expected: exception thrown (but does not happen).
    }
}

它只保存记录并将RowVersion更新为0x0000000000002B0C而不是并发异常。

我错过了什么?

我需要一种方法来检测更改,删除等,以防止保存脏数据。我想我可以滚动自己的检查,但实际的对象很复杂,有很多嵌套的子对象(一个或多个级别)。

关于此的最佳做法的任何指示也将受到赞赏......

3 个答案:

答案 0 :(得分:8)

我得到了这个工作。在我的问题的第5步中,我更改了这一行:

dbCompany.RowVersion = updatedCompany.RowVersion;  

对此:

Context.Entry(dbCompany).OriginalValues["RowVersion"] = updatedCompany.RowVersion;

现在,EF在尝试保存脏数据时会抛出DbUpdateConcurrencyException!

答案 1 :(得分:0)

我正在使用Entity Framework的抽象,但无法访问EF上下文,但是我发现写作

updatedCompany.RowVersion.CopyTo(dbCompany.RowVersion, 0);

解决了问题。

答案 2 :(得分:0)

在@ Lars335之后,我写了一个小助手方法来重置并发令牌的原始值

    public static void ResetConcurrencyValues(this DbContext context, Object entity) {
        var lEntry = context.Entry(entity);
        foreach (var lProperty in lEntry.Metadata.GetProperties().Where(x => x.IsConcurrencyToken)) {
            lEntry.OriginalValues[lProperty] = lEntry.CurrentValues[lProperty];
        }
    }

然后使记录更新成为

    _context.Update(user);
    _context.ResetConcurrencyValues(user);
    var lCount = await _context.SaveChangesAsync(cancellationToken);

但是,我也很想,如果令牌的 current 值与存储的值不同,则应该引发并发异常。在进行了冲突的更改之前,无法重新读取更新的记录,因此永远不会包含原始值。