You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET7中UserManager更新用户失败:出现ConcurrencyFailure并发错误

解决.NET7中ASP.NET Identity更新用户导航属性时的乐观并发失败问题

核心原因

ASP.NET Identity默认通过SecurityStamp字段做乐观并发校验,当你获取用户后,若数据库中用户的SecurityStamp已被其他操作修改,或上下文跟踪的用户实体并发校验字段与数据库不一致,就会触发ConcurrencyFailure错误。此外,导航属性(如Websites)未正确加载或跟踪也可能导致更新失败。

具体解决方案

1. 确保加载导航属性并正确跟踪实体

默认FindByIdAsync不会自动加载导航属性,需显式包含Websites以确保上下文正确跟踪集合变更:

var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await _userManager.Users
    .Include(u => u.Websites)
    .FirstOrDefaultAsync(u => u.Id == userId);

if (user == null)
{
    return NotFound();
}

user.Websites.Add(website);

2. 配置自定义行版本作为并发令牌(替代或补充SecurityStamp)

若使用自定义行版本字段,需在ApplicationUser类中定义并配置为并发令牌:

public class ApplicationUser : IdentityUser
{
    public List<Website> Websites { get; set; } = new List<Website>();
    
    // 自定义行版本字段
    [Timestamp]
    public byte[]? RowVersion { get; set; }
}

在DbContext的OnModelCreating中配置该字段为并发令牌:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    
    modelBuilder.Entity<ApplicationUser>()
        .Property(u => u.RowVersion)
        .IsRowVersion()
        .IsConcurrencyToken();
}

3. 处理并发冲突的重试逻辑

若存在并发修改的可能,可捕获DbUpdateConcurrencyException并重试更新:

var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var retryCount = 3;

while (retryCount > 0)
{
    try
    {
        var user = await _userManager.Users
            .Include(u => u.Websites)
            .FirstOrDefaultAsync(u => u.Id == userId);
        
        if (user == null)
        {
            return NotFound();
        }

        user.Websites.Add(website);
        var result = await _userManager.UpdateAsync(user);
        
        if (result.Succeeded)
        {
            return Ok(user.Websites);
        }
        
        return BadRequest(result.Errors);
    }
    catch (DbUpdateConcurrencyException)
    {
        retryCount--;
        if (retryCount == 0)
        {
            return StatusCode(StatusCodes.Status409Conflict, "用户信息已被其他操作修改,请重试");
        }
        // 等待片刻后重试(可选)
        await Task.Delay(500);
    }
}

4. 强制更新SecurityStamp(若确认无其他并发修改)

如果确定当前用户实体是最新的,可手动更新SecurityStamp后再执行更新:

user.Websites.Add(website);
// 生成新的SecurityStamp
user.SecurityStamp = Guid.NewGuid().ToString();
var result = await _userManager.UpdateAsync(user);

关键注意点

  • 避免在多个上下文实例中跟踪同一用户实体,确保每次更新都使用最新的上下文和用户数据。
  • 若使用自定义并发字段,需确保Identity框架识别该字段为并发令牌,否则仍会使用默认的SecurityStamp校验。

内容的提问来源于stack exchange,提问作者Edvin Kovacevic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 00:42:39