如何正确使用Exists?用户注册重复邮箱仍被保存问题排查
问题修复方案
核心问题排查
你的代码中,重复邮箱用户仍被保存的可能原因如下:
- 角色判断限制:仅当用户角色为
Role.USER时才触发邮箱唯一性检查,如果新用户角色不是USER,会直接跳过检查执行保存。 - 并发场景漏洞:代码层面的检查无法完全避免并发请求,两个请求同时通过邮箱检查后,会同时插入重复数据。
- Exists方法逻辑问题:如果
Exists方法查询的是DbContext本地缓存而非数据库,可能无法正确检测到已存在的用户。
修复步骤
1. 扩大邮箱唯一性检查范围
将邮箱检查逻辑扩展到所有新用户(ID=0的用户),确保所有注册用户都强制邮箱唯一:
public override int Save(User entity, bool saveChanges = true, bool IgnoreValidation = false, bool checkConcurrency = true) { try { bool sendMail = (entity.ID == 0); string clearPassword = null; // 新用户优先执行邮箱唯一性检查 if (entity.ID == 0) { if (Exists(x => x.Email == entity.Email)) { throw new EntityValidationException("系统中已存在使用该邮箱注册的用户"); } // 密码处理逻辑 clearPassword = string.IsNullOrEmpty(entity.Password) ? _passwordService.MakePassword() : entity.Password; entity.Password = _passwordService.EncryptSHA256(clearPassword); } int userSavedID = base.Save(entity, saveChanges); if (sendMail) _emailService.CreateNewCredentialEmail(entity, clearPassword); return userSavedID; } catch (Exception ex) { throw ex; } }
2. 数据库层面添加唯一约束
这是防止重复数据的最可靠手段,即使代码层面出现漏洞,数据库会直接阻止插入:
在SQL数据库的Users表上,为Email字段添加唯一约束:
ALTER TABLE Users ADD CONSTRAINT UQ_Users_Email UNIQUE (Email);
如果使用EF Core,也可以通过代码配置:
// 数据注解方式(在User实体类中) [Index(nameof(Email), IsUnique = true)] public string Email { get; set; } // 或Fluent API(在DbContext的OnModelCreating方法中) modelBuilder.Entity<User>() .HasIndex(u => u.Email) .IsUnique();
3. 验证Exists方法的实现
确保Exists方法是查询数据库而非本地缓存,比如EF环境下的正确实现:
public bool Exists(Expression<Func<User, bool>> predicate) { // 使用DbSet.Any()查询数据库,而非Local.Any() return _dbContext.Users.Any(predicate); }
额外说明
- 移除了原代码中
x.ID != entity.ID的条件,因为新用户ID为0,数据库中不存在ID=0的用户,该条件多余。 - 数据库唯一约束会在插入重复数据时抛出异常,需要在上层代码中捕获并返回友好提示。
内容的提问来源于stack exchange,提问作者Jany
相关产品推荐
相关产品推荐

