如何在ASP.NET Core Identity中通过事务实现用户角色修改?
报错原因
该报错核心有两个诱因:
- 使用
TransactionScope时未开启异步流支持,异步操作的上下文无法被事务正确感知,导致后续操作拿到的数据库连接不属于当前事务范围。 - 原有代码使用
.Result同步阻塞异步方法,导致线程上下文切换混乱,不同步的操作尝试占用已被事务绑定的连接。
推荐解决方案:使用EF Core本地事务
ASP.NET Core Identity默认基于EF Core实现存储,直接复用Identity对应的DbContext开启事务即可,不需要额外依赖,适配性最好:
// 类中先注入依赖 private readonly UserManager<User> _userManager; private readonly ApplicationDbContext _dbContext; // 构造函数注入实例 public YourService(UserManager<User> userManager, ApplicationDbContext dbContext) { _userManager = userManager; _dbContext = dbContext; } // 改为异步方法,全程使用await避免阻塞 public async Task UpdateRolesAsync(long userId, List<string> roles) { // 开启EF Core本地事务 using var transaction = await _dbContext.Database.BeginTransactionAsync(); try { var user = await _userManager.FindByIdAsync(userId.ToString()); if (user == null) { throw new Exception("指定用户不存在"); } var existingRoles = (await _userManager.GetRolesAsync(user)).ToList(); var newRoles = roles.Where(i => !existingRoles.Contains(i)).ToList(); var rolesToBeDeleted = existingRoles.Where(i => !roles.Contains(i)).ToList(); if (rolesToBeDeleted.Any()) { var removeResult = await _userManager.RemoveFromRolesAsync(user, rolesToBeDeleted); if (!removeResult.Succeeded) { throw new Exception($"移除用户角色失败: {removeResult.Serialize()}"); } } if (newRoles.Any()) { var addResult = await _userManager.AddToRolesAsync(user, newRoles); if (!addResult.Succeeded) { throw new Exception($"添加用户角色失败: {addResult.Serialize()}"); } } // 所有操作执行成功,提交事务 await transaction.CommitAsync(); } catch { // 出现异常回滚所有修改 await transaction.RollbackAsync(); throw; } }
备选方案:使用TransactionScope
如果业务必须使用TransactionScope,只需要添加异步流支持参数,并且全程使用异步调用即可解决报错:
public async Task UpdateRolesAsync(long userId, List<string> roles) { // 传入TransactionScopeAsyncFlowOption.Enabled开启异步上下文事务传播 using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); var user = await _userManager.FindByIdAsync(userId.ToString()); if (user == null) { throw new Exception("指定用户不存在"); } var existingRoles = (await _userManager.GetRolesAsync(user)).ToList(); var newRoles = roles.Where(i => !existingRoles.Contains(i)).ToList(); var rolesToBeDeleted = existingRoles.Where(i => !roles.Contains(i)).ToList(); if (rolesToBeDeleted.Any()) { var removeResult = await _userManager.RemoveFromRolesAsync(user, rolesToBeDeleted); if (!removeResult.Succeeded) { throw new Exception($"移除用户角色失败: {removeResult.Serialize()}"); } } if (newRoles.Any()) { var addResult = await _userManager.AddToRolesAsync(user, newRoles); if (!addResult.Succeeded) { throw new Exception($"添加用户角色失败: {addResult.Serialize()}"); } } scope.Complete(); }
该方案需要注意:.NET Core 2.1及以上版本才支持TransactionScope的异步流配置,如果使用SQL Server且事务涉及多个不同连接,需要开启MSDTC服务支持分布式事务。
内容的提问来源于stack exchange,提问作者Ali EXE
相关产品推荐
相关产品推荐

