为何无法删除用Identity库创建的用户间多对多关联?
问题场景
使用ASP.NET Identity创建用户,并通过AppUser类定义用户间的多对多关联:
public class AppUser:IdentityUser { public AppUser() { MainUser = new List<AppUser>(); UserConnection = new List<AppUser>(); } public ICollection<AppUser> MainUser { get; set; } public ICollection<AppUser> UserConnection { get; set; } }
基类中获取当前用户的代码:
protected AppUser CurrentUser => userManager.FindByNameAsync(User.Identity.Name).Result;
添加关联的接口可正常工作:
[HttpPost] public IActionResult AddConnection(UserViewModel model) { AppUser searchedUser = userManager.FindByNameAsync(model.UserName).Result; CurrentUser.UserConnection.Add(searchedUser); Context.SaveChanges(); return Redirect($"FindConnection/{model.Search}"); }
但删除关联的接口无法生效:
[HttpPost] public IActionResult RemoveConnection(UserViewModel model) { AppUser user = Context.Users.FirstOrDefault(x => x.UserName == model.UserName); CurrentUser.UserConnection.Remove(user); Context.SaveChanges(); return Redirect($"FindConnection/{model.Search}"); }
核心原因
1. 关联集合未被加载(延迟加载问题)
CurrentUser通过userManager.FindByNameAsync获取时,EF默认不会自动加载导航属性UserConnection(未启用延迟加载或未显式加载)。此时CurrentUser.UserConnection为空集合,Remove(user)操作找不到要删除的关联项,自然不会生成删除语句。
而添加关联时,即使集合未加载,Add(searchedUser)会直接将新关联加入集合,EF会跟踪新增操作并生成插入语句,因此可以生效。
2. 实体上下文跟踪不一致
删除方法中,user是从Context.Users直接查询的实例,而CurrentUser来自userManager的上下文。如果两个实例不属于同一个DbContext跟踪范围,EF无法识别CurrentUser.UserConnection中包含该user,导致Remove操作无效。
3. 隐式多对多的EF处理逻辑
EF Core(5.0+支持的隐式多对多)需要确保关联的两端实体都被上下文跟踪,且导航集合已加载,才能正确识别并删除中间表的关联记录。
解决方案
方案1:显式加载当前用户的关联集合
在删除方法中重新获取当前用户,并显式包含UserConnection集合,确保EF能跟踪到关联关系:
[HttpPost] public async Task<IActionResult> RemoveConnection(UserViewModel model) { // 显式加载当前用户的UserConnection集合 var currentUser = await Context.Users .Include(u => u.UserConnection) .FirstOrDefaultAsync(u => u.UserName == User.Identity.Name); if(currentUser == null) return NotFound(); var user = await Context.Users.FirstOrDefaultAsync(x => x.UserName == model.UserName); if(user != null) { currentUser.UserConnection.Remove(user); await Context.SaveChangesAsync(); } return Redirect($"FindConnection/{model.Search}"); }
方案2:显式加载现有集合
如果要保留CurrentUser的定义,可在删除前手动加载UserConnection集合:
[HttpPost] public async Task<IActionResult> RemoveConnection(UserViewModel model) { // 显式加载CurrentUser的UserConnection await userManager.Context.Entry(CurrentUser) .Collection(u => u.UserConnection) .LoadAsync(); var user = await Context.Users.FirstOrDefaultAsync(x => x.UserName == model.UserName); if(user != null) { CurrentUser.UserConnection.Remove(user); await Context.SaveChangesAsync(); } return Redirect($"FindConnection/{model.Search}"); }
方案3:修复同步调用的潜在问题
原代码中使用.Result同步等待异步方法,可能导致死锁或上下文状态异常。建议统一改为async/await模式,避免同步阻塞带来的上下文跟踪问题。
内容的提问来源于stack exchange,提问作者Berk KARASU

