Sim*_*n k 0 c# entity-framework-core asp.net-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);
}
}
Run Code Online (Sandbox Code Playgroud)
这引发了 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)
Run Code Online (Sandbox Code Playgroud)
我知道这个例外通常表示比赛条件,但我不知道为什么会这样。
难道我做错了什么?
编辑
我发布的用户对象看起来像这样:
"User": {
"Email": "",
"FirstName": "",
"LastName": "",
"Gender": "",
"Affiliation": {
"isStudent": true,
"isEmployee": false
}
...
}
Run Code Online (Sandbox Code Playgroud)
实体框架核心使用乐观并发:
在乐观并发模型中,如果在用户从数据库中接收到值之后,另一个用户在第一个用户尝试修改它之前修改了该值,则认为发生了违规。
将此与悲观并发进行对比:
在悲观的并发模型中,更新行的用户将建立锁。在用户完成更新并释放锁之前,没有其他人可以更改该行。
为了实现乐观并发,IdentityUser该类包含一个ConcurrencyStamp属性(以及数据库中的相应列),该属性是GUID的字符串表示形式:
public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();
Run Code Online (Sandbox Code Playgroud)
每次将用户保存到数据库时,ConcurrencyStamp都会将其设置为新的GUID。
以删除用户为例,DELETE发送到服务器的SQL语句的简化版本可能类似于以下内容:
public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();
Run Code Online (Sandbox Code Playgroud)
当CONCURRENCY_STAMP上面的SQL语句中的值与给定用户的数据库中存储的值不匹配时,会出现您收到的错误消息。这样可以确保,如果您从数据库(包含特定的ConcurrencyStamp)中检索用户,则只有在其他地方未进行其他更改(因为您提供ConcurrencyStamp的数据库中存在的值相同)时,才可以将更改保存到数据库中。
从ConcurrencyStamp上面的定义可以看到,该属性默认为新属性GUID-每次IdentityUser创建(或子类)时,它都会获得一个新ConcurrencyStamp值。在您的示例中,通过将User传递给您的DeleteAsync操作,ASP.NET Core Model-Binding首先创建的新实例,User然后设置JSON有效负载中存在的属性。由于ConcurrencyStamp有效负载中没有值,User因此最终将得到一个新 ConcurrencyStamp值,该值与数据库中的值不匹配。
为避免此问题,可以将ConcurrencyStamp值添加到客户端发送的有效负载中。但是,我不建议这样做。解决这个问题的最简单,最安全的办法是发送Id的User作为有效载荷,检索User自己使用_userManager.FindByIdAsync,然后使用该实例来执行删除操作。这是一个例子:
[HttpPost("Delete/{id}", Name = "DeleteRoute")]
[Authorize(Roles = "SuperUser")]
public async Task<IActionResult> DeleteAsync(string id)
{
Console.WriteLine("Deleting user: " + id);
try {
var user = await _userManager.FindByIdAsync(id);
if(user == null)
// ...
await _userManager.DeleteAsync(user);
return Ok();
} catch(Exception e) {
return BadRequest(e.Message);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
428 次 |
| 最近记录: |