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

如何优化控制器方法,避免两次查询用户数据?

解决方案:一次查询获取所有所需数据

核心思路是跳过两次冗余查询,直接从Claims提取邮箱后,用一次数据库查询获取所有需要的用户信息、统计数据和角色列表,同时重构Token生成方法,不再依赖完整的User实体。

步骤1:重构控制器方法,合并查询逻辑

直接从当前请求的Claims中提取邮箱,然后通过一次查询获取所有需要的数据(包括角色),避免多次查询数据库:

[HttpGet]
public async Task<IActionResult> GetCurrentUser()
{
    // 从Claims中提取用户邮箱
    var email = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value;
    if (string.IsNullOrEmpty(email))
        return Unauthorized(new ApiResponse(401));

    // 一次查询获取所有所需数据:基础属性、统计数据、角色列表
    var userData = await _dbContext.Users
        .Where(u => u.Email == email)
        .Select(u => new
        {
            u.Id,
            u.Email,
            u.UserName,
            u.Hosted,
            u.Instructed,
            u.Attended,
            u.IsBoarded,
            Likers = u.Likers.Count(),
            u.Rating,
            u.CreatedDate,
            PhotoUrl = u.IsBoarded ? u.UserPhotos.FirstOrDefault(p => p.IsMain).Url : "assets/images/user.png",
            Age = u.DateOfBirth.CalculateAge(),
            u.DateOfBirth,
            ExperienceLevel = u.ExperienceLevel.GetEnumName(),
            Points = u.UserPoints.Sum(p => p.Points),
            Tokens = u.UserTokens.Sum(p => p.Tokens),
            Memberships = u.YogabandMemberships.Count(x => x.Status == YogabandMemberStatus.Active),
            u.IsInstructor,
            // 通过导航属性获取角色(需确保User实体配置了UserRoles导航)
            Roles = u.UserRoles.Select(ur => ur.Role.Name).ToList()
        })
        .FirstOrDefaultAsync();

    if (userData == null)
        return Unauthorized(new ApiResponse(401));

    // 生成Token(传递所需参数而非完整实体)
    var token = await _tokenService.CreateToken(userData.Id, userData.Email, userData.UserName, userData.Roles);

    // 组装返回结果
    var result = new
    {
        userData.Id,
        userData.Email,
        userData.UserName,
        userData.Hosted,
        userData.Instructed,
        userData.Attended,
        userData.IsBoarded,
        userData.Likers,
        userData.Rating,
        userData.CreatedDate,
        userData.PhotoUrl,
        userData.Age,
        userData.DateOfBirth,
        userData.ExperienceLevel,
        userData.Points,
        userData.Tokens,
        userData.Memberships,
        Token = token,
        userData.IsInstructor
    };

    return Ok(result);
}

步骤2:重构CreateToken方法,依赖具体参数而非完整实体

修改Token生成逻辑,不再需要传入整个User对象,只接收生成Claims必需的参数:

public async Task<string> CreateToken(Guid userId, string email, string userName, List<string> roles)
{
    var claims = new List<Claim>
    {
        new Claim(JwtRegisteredClaimNames.Email, email),
        new Claim(JwtRegisteredClaimNames.GivenName, userName),
        new Claim(JwtRegisteredClaimNames.NameId, userId.ToString()),
    };

    // 直接传入角色列表,避免调用UserManager.GetRolesAsync的额外查询
    claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));

    var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);

    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(claims),
        Expires = DateTime.Now.AddDays(365),
        SigningCredentials = creds,
        Issuer = _config["Token:Issuer"]
    };

    var tokenHandler = new JwtSecurityTokenHandler();
    var token = tokenHandler.CreateToken(tokenDescriptor);

    return tokenHandler.WriteToken(token);
}

关键优化点

  1. 消除冗余查询:从Claims直接取邮箱,一次查询覆盖所有需要的数据(包括角色),彻底避免两次User实体查询和UserManager.GetRolesAsync的额外查询。
  2. 避免加载完整实体:通过Select投影只获取需要的字段,不用加载User实体的30列数据,节省数据库带宽。
  3. 降低耦合:Token生成方法不再依赖完整User实体,只依赖必要参数,代码更健壮。

注意事项

  • 确保你的User实体已配置UserRoles导航属性(关联AspNetUserRoles表),否则需要手动Join_dbContext.UserRoles和_dbContext.Roles来获取角色名称:
    Roles = _dbContext.UserRoles
        .Where(ur => ur.UserId == u.Id)
        .Join(_dbContext.Roles, ur => ur.RoleId, r => r.Id, (ur, r) => r.Name)
        .ToList()
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 10:17:55