首页 > 解决方案 > 为什么会抛出 DbUpdateConcurrencyException?

问题描述

我正在使用 SQL 服务器,并且我有一个表,其目的是保存树状结构:

create table TableName (
   Id                   bigint               identity,
   Name                 nvarchar(50)         null,
   RootId               bigint               null,
   ParentId             bigint               null,
   Path                 nvarchar(100)        null,
   constraint PK_TableName primary key (Id)
)

“路径”列值由 INSTEAD OF INSERT、UPDATE 触发器生成。我正在使用 EFCore 3.1,每次尝试将记录添加到表中时,都会抛出 DbUpdateConcurrencyException。

我错过了什么 - 我该如何解决这个问题?

顺便说一句,当我禁用触发器插入通道时,当我发送常规 INSERT 命令时触发器工作。

感谢 Panagiotis 为您解答。我理解逻辑,但它仍然不起作用。我试过这个:

protected virtual void MapTableName(EntityTypeBuilder<TableName> config)
{
    config.ToTable("TableName");
    config.HasKey(t => t.Id);
    config.Property(t => t.Id).ValueGeneratedOnAdd().IsConcurrencyToken();
    config.Property(t => t.Name).IsConcurrencyToken().HasMaxLength(50);
    config.Property(t => t.Description).IsConcurrencyToken().HasMaxLength(100);
    config.Property(t => t.RootId).IsConcurrencyToken();
    config.Property(t => t.ParentId).IsConcurrencyToken();
    config.Property(t => t.Path).HasMaxLength(100).ValueGeneratedOnAddOrUpdate();
    config.Property(t => t.TypeId).IsConcurrencyToken();
    config.Property(t => t.IsActive).IsConcurrencyToken();
    config.HasOne(t => t.LocationType).WithMany(t => t.TableNames).HasForeignKey(t => t.TypeId);
    config.HasOne(t => t.ParentTableName).WithMany(t => t.ChilTableNames).HasForeignKey(t => t.ParentId);
    config.HasOne(t => t.RootTableName).WithMany(t => t.ChildTableNamesAll).HasForeignKey(t => t.RootId);
}

但我得到相同的答案:

        "Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions."

标签: sqlsql-serverentity-framework-core

解决方案


问题是Path.

EF Core 默认使用乐观并发,假设冲突(即另一个连接对同一记录的更改)很少见。为确保自加载记录后值未更改,EF Core 将检查rowversion列的值(如果存在),或将所有原始属性值与表的值进行比较。如果Path在 EF 不知道的情况下更改,则会出现并发冲突。

解决此问题的最佳方法是向表中添加一rowversion列,并将其添加到具有Timestamp属性(rowversion 的旧名称)的模型中。在 SQL Server 中,每次更新时服务器都会自动更新 rowversion。这样,并发检查只使用一个小的二进制值:

class MyClass
{
    public int Id {get;set;}
    public string Name{get;set;}

    public string Path {get;set;}

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

另一种选择是仅使用ConcurrencyCheck属性标记几个属性。在这种情况下,除了: Path

{
    [ConcurrencyCheck]
    public int Id {get;set;}
    [ConcurrencyCheck]
    public string Name{get;set;}

    public string Path {get;set;}
}

推荐阅读