如何优化控制器方法,避免两次查询用户数据?
解决方案:一次查询获取所有所需数据
核心思路是跳过两次冗余查询,直接从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); }
关键优化点
- 消除冗余查询:从Claims直接取邮箱,一次查询覆盖所有需要的数据(包括角色),彻底避免两次User实体查询和UserManager.GetRolesAsync的额外查询。
- 避免加载完整实体:通过Select投影只获取需要的字段,不用加载User实体的30列数据,节省数据库带宽。
- 降低耦合: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
相关产品推荐
相关产品推荐

