C# Identity Usermanager DeleteAsync DbUpdateConcurrencyException

C# Identity Usermanager DeleteAsync DbUpdateConcurrencyException,c#,asp.net-core,entity-framework-core,asp.net-core-identity,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("Deleti

我试图通过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);
        }

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

我做错什么了吗

编辑

我发布的用户对象如下所示:

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

实体框架核心使用:

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

与悲观并发性相比:

…在悲观并发模型中,更新行的用户建立锁。在用户完成更新并释放锁之前,其他人无法更改该行

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

public virtual string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();
每次将用户保存到数据库时,
ConcurrencyStamp
都会设置为新的GUID

以删除用户为例,发送到服务器的
DELETE
SQL语句的简化版本可能如下所示:

从dbo.AspNetUsers中删除
其中Id=''和ConcurrencyStamp=''
当上面SQL语句中的
并发\u STAMP
值与数据库中存储的给定用户的值不匹配时,会出现您收到的错误消息。这确保了如果从数据库(其中包含特定的
ConcurrencyStamp
)检索用户,则只有在其他地方未进行任何更改时(因为您提供的是数据库中存在的相同
ConcurrencyStamp
值),才能保存对数据库的更改

从上面的
ConcurrencyStamp
定义中可以看到,该属性默认为一个新的
GUID
——每次创建
IdentityUser
(或子类)时,它都会获得一个新的
ConcurrencyStamp
值。在您的示例中,使用传递到
DeleteAsync
操作的
User
,ASP.NET核心模型绑定首先创建
User
的新实例,然后设置JSON负载中存在的属性。由于有效负载中没有
ConcurrencyStamp
值,
User
将以新的
ConcurrencyStamp
值结束,该值与数据库中的值不匹配

为了避免此问题,您可以将
ConcurrencyStamp
值添加到从客户端发送的有效负载中。但是,我不建议这样做。解决此问题的最简单和最安全的方法是将
用户的
Id
作为有效负载发送,使用
\u 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);
    }
}
[HttpPost(“Delete/{id}”,Name=“DeleteRoute”)]
[授权(角色=“超级用户”)]
公共异步任务DeleteAsync(字符串id)
{
Console.WriteLine(“删除用户:+id”);
试一试{
var user=await\u userManager.FindByIdAsync(id);
if(user==null)
// ... 
wait_userManager.deleteAync(用户);
返回Ok();
}捕获(例外e){
返回请求(e.Message);
}
}

在保存过程中,如果意外的行数受到影响,则会发生并发冲突。这通常是因为数据库中的数据在加载到内存后已被修改。()在您的情况下,控制器接收的用户对象在数据库中可能不具有相同的值。正如@KirkLarkin所建议的,您可以使用用户ID获取用户对象,然后调用delete方法。
[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);
    }
}