.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
相关产品推荐
相关产品推荐

