[英]Identity Usermanager DeleteAsync DbUpdateConcurrencyException
我試圖通過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
}
...
}
實體框架核心使用樂觀並發 :
在樂觀並發模型中,如果在用戶從數據庫中接收到值之后,另一個用戶在第一個用戶嘗試修改值之前修改了該值,則認為發生了違規。
將此與悲觀並發進行對比:
在悲觀的並發模型中,更新行的用戶將建立鎖。 在用戶完成更新並釋放鎖之前,沒有其他人可以更改該行。
為了實現樂觀並發, 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
)中檢索用戶,則只有在其他地方沒有其他更改的情況下,才能將更改保存到數據庫中(因為您要提供數據庫中存在的相同ConcurrencyStamp
值) )。
從上面的ConcurrencyStamp
定義可以看到,該類屬性默認為新的GUID
每次創建IdentityUser
(或子類)時,都會為其分配新的ConcurrencyStamp
值。 在您的示例中,通過將User
傳遞給DeleteAsync
操作,ASP.NET Core Model-Binding首先創建一個User
的新實例,然后設置JSON有效負載中存在的屬性。 由於有效負載中沒有ConcurrencyStamp
值,因此User
最終將獲得一個新的 ConcurrencyStamp
值,該值當然與數據庫中的值不匹配。
為避免此問題,您可以將ConcurrencyStamp
值添加到客戶端發送的有效負載中。 但是,我不建議這樣做。 解決此問題的最簡單,最安全的方法是,簡單地發送User
的Id
作為有效載荷,使用_userManager.FindByIdAsync
檢索User
本身,然后使用該實例執行刪除。 這是一個例子:
[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);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.