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

ASP.NET Core Identity并发创建用户重复邮箱与锁内异步问题咨询

解决ASP.NET Core Identity并发创建重复邮箱用户的问题

问题根源

你设置的RequireUniqueEmail = true仅在业务层做前置检查,并发场景下会出现竞态条件:多个请求同时通过“邮箱是否存在”的检查,随后都执行插入操作。如果数据库的Email字段没有唯一约束,就会生成重复数据。而lock关键字不支持异步代码,所以直接用lock包裹await方法会报错。

解决方案

1. 数据库层面添加唯一约束(最可靠的最终防线)

通过EF Core的Fluent API给User实体的Email字段配置唯一索引,让数据库强制保证邮箱唯一性:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    
    // 给Email字段添加唯一约束
    modelBuilder.Entity<User>()
        .HasIndex(u => u.Email)
        .IsUnique();
}

执行EF迁移并更新数据库后,数据库会直接拦截重复邮箱的插入请求,抛出约束冲突异常。

2. 业务层用异步同步原语替代lock

使用SemaphoreSlim实现异步锁,保证同一时间只有一个请求执行“邮箱检查+用户创建”的逻辑:

// 定义静态信号量,初始计数1,最多允许1个请求进入临界区
private static readonly SemaphoreSlim _createUserSemaphore = new SemaphoreSlim(1, 1);

public async Task<IdentityResult> CreateUniqueUserAsync(User userToCreate, string userPassword)
{
    try
    {
        // 等待进入临界区
        await _createUserSemaphore.WaitAsync();
        
        // 先检查邮箱是否已存在
        var existingUser = await _userManager.FindByEmailAsync(userToCreate.Email);
        if (existingUser != null)
        {
            return IdentityResult.Failed(new IdentityError { Description = "邮箱已存在" });
        }
        
        // 执行用户创建
        return await _userManager.CreateAsync(userToCreate, userPassword);
    }
    finally
    {
        // 无论成功失败,都释放信号量
        _createUserSemaphore.Release();
    }
}

3. 捕获数据库约束异常并转换为业务错误

即使有业务层的异步锁,极端情况(比如外部直接操作数据库)仍可能触发数据库约束,需要捕获异常并转换为用户能理解的错误:

public async Task<IdentityResult> CreateUniqueUserAsync(User userToCreate, string userPassword)
{
    try
    {
        await _createUserSemaphore.WaitAsync();
        
        var existingUser = await _userManager.FindByEmailAsync(userToCreate.Email);
        if (existingUser != null)
        {
            return IdentityResult.Failed(new IdentityError { Description = "邮箱已存在" });
        }
        
        return await _userManager.CreateAsync(userToCreate, userPassword);
    }
    catch (DbUpdateException ex)
    {
        // 识别SQL Server唯一约束冲突错误(错误码2601)
        if (ex.InnerException is SqlException sqlEx && sqlEx.Number == 2601)
        {
            return IdentityResult.Failed(new IdentityError { Description = "邮箱已存在" });
        }
        // 其他异常重新抛出
        throw;
    }
    finally
    {
        _createUserSemaphore.Release();
    }
}

最优实践

推荐同时使用数据库唯一约束 + 业务层异步锁 + 异常捕获的三层保障,既从根源阻止重复数据,又保证业务层的友好错误提示。

内容的提问来源于stack exchange,提问作者Milad Ahmadi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 20:12:34