【问题标题】:Why is DbUpdateConcurrencyException thrown?为什么会抛出 DbUpdateConcurrencyException?
【发布时间】:2021-03-19 16:32:10
【问题描述】:

我正在使用 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."

【问题讨论】:

  • 因为 EF 认为您正在添加一条新记录,所以它期望在 @@IDENTITY 中看到一个值。但事实并非如此,因为您正在更新现有记录。
  • @CaptainKenpachi 这不是并发异常的意义
  • 我能想到两件事:将触发器的逻辑移动到应用程序层,或者为 INSERT 命令生成 SQL,然后针对 context.database 执行它.
  • 问题出在Path。 EF Core 默认使用乐观并发,假设冲突(即另一个连接对同一记录的更改)很少见。为确保自加载记录后值未更改,EF Core 将检查 rowversion 列的值(如果存在),或将 all 原始属性值与表的值进行比较。如果 Path 在 EF 不知情的情况下更改,则会出现并发冲突
  • @PanagiotisKanavos 添加新记录时没有并发检查。问题显然是触发器弄乱了insert into table values ...; select Id from table where @@ROWCOUNT = 1 AND [Id] = scope_identity() 用于检索标识值的典型“自动增量”逻辑。因此,“预计会影响 1 行,但实际上影响了 0 行” 部分错误消息(其余部分只是误导)。

标签: sql sql-server entity-framework-core


【解决方案1】:

问题是Path

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

解决此问题的最佳方法是添加到 add a rowversion column to the table 并使用 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; }
}

另一个选项是mark only a few of the propertiesConcurrencyCheck 属性。在这种情况下,每个属性 除了 Path :

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

    public string Path {get;set;}
}

【讨论】:

    猜你喜欢
    • 2021-12-20
    • 2021-03-05
    • 2023-03-14
    • 2021-04-09
    • 2014-12-18
    • 2013-12-08
    • 2010-09-27
    • 2015-05-02
    • 2017-05-22
    相关资源
    最近更新 更多