身份用户管理器DeleteAsync DbUpdateConcurrencyException

时间:2018-08-28 07:17:12

标签: c# asp.net-core entity-framework-core asp.net-core-identity

我正在尝试通过webapi后面的aspnetcore.identity UserManager删除用户。

    [HttpPost("Delete", Name = "DeleteRoute")]
    [Authorize(Roles = "SuperUser")]
    public async Task<IActionResult> DeleteAsync([FromBody] User user)
    {
        Console.WriteLine("Deleting user: " + user.Id);
        try {
            await _userManager.DeleteAsync(user);
            return Ok();
        } catch(Exception e) {
            return BadRequest(e.Message);
        }

    }

这会引发DbUpdateConcurrencyException

   Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 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.
   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected)
   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithoutPropagationAsync(Int32 commandIndex, RelationalDataReader reader, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(DbContext _, ValueTuple`2 parameters, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IReadOnlyList`1 entriesToSave, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 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.
   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected)
   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithoutPropagationAsync(Int32 commandIndex, RelationalDataReader reader, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(DbContext _, ValueTuple`2 parameters, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IReadOnlyList`1 entriesToSave, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)

我知道此异常通常表示比赛条件,但我不知道为什么会发生这种情况。

我做错什么了吗?

编辑

我发布的用户对象看起来像这样:

"User": {
 "Email": "",
 "FirstName": "",
 "LastName": "",
 "Gender": "",
 "Affiliation": {
     "isStudent": true,
     "isEmployee": false
   }
   ...
}

1 个答案:

答案 0 :(得分:4)

Entity Framework Core使用Optimistic Concurrency

  

在乐观并发模型中,如果在用户从数据库接收到一个值之后,另一个用户在第一个用户尝试修改它之前修改了该值,则认为发生了违规。

将此与悲观并发进行对比:

  

...在悲观并发模型中,更新行的用户将建立锁。在用户完成更新并释放锁之前,没有其他人可以更改该行。

为了实现乐观并发性,IdentityUser类包含一个ConcurrencyStamp属性(以及数据库中的相应列),它是GUID的字符串表示形式:

public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();

每次将用户保存到数据库时,ConcurrencyStamp都会设置为新的GUID。

以删除用户为例,发送到服务器的DELETE SQL语句的简化版可能类似于以下内容:

DELETE FROM dbo.AspNetUsers WHERE Id = '<USER_ID>' AND ConcurrencyStamp = '<CONCURRENCY_STAMP>'

当上面的SQL语句中的CONCURRENCY_STAMP值与给定用户的数据库中存储的值不匹配时,会出现错误消息。这样可以确保,如果您从数据库(包含特定的ConcurrencyStamp)中检索用户,则仅当未在其他地方进行其他更改(如提供相同的{{1 }}数据库中存在的值。

从上面的ConcurrencyStamp定义中可以看到,class属性默认为新的ConcurrencyStamp-每次创建GUID(或子类)时,都会为其分配一个新的IdentityUser值。在您的示例中,使用ConcurrencyStamp传递到您的User操作中,ASP.NET Core Model-Binding首先创建一个DeleteAsync的新实例,然后设置存在于其中的属性。 JSON有效负载。由于有效负载中没有User值,因此ConcurrencyStamp将以 new User值结尾,该值当然与数据库中的值不匹配

为避免此问题,您可以可以ConcurrencyStamp值添加到客户端发送的有效负载中。但是,我不建议这样做。解决此问题的最简单,最安全的方法是简单地发送ConcurrencyStamp中的Id作为有效载荷,使用User检索User本身,然后使用实例即可执行删除操作。这是一个示例:

_userManager.FindByIdAsync