如何使用EF Core LINQ将多对多查询转换为含角色名的UserDTO?
问题描述
我有两个由EF Core Scaffold自动生成的实体类:
public partial class Role : IEntityWithDTO<ClientRoleDTO> { public Guid Id { get; set; } public string Name { get; set; } = null!; public virtual ICollection<User> Users { get; set; } = new List<User>(); } public partial class User : IEntityWithDTO<UserDTO> { public Guid Id { get; set; } public string Login { get; set; } = null!; public string Pass { get; set; } = null!; public virtual ICollection<Role> Roles { get; set; } = new List<Role>(); }
对应的UserDTO类定义如下:
public class UserDTO: IDTO { public required Guid Id { get; set; } public required string Login { get; set; } public List<string>? Roles { get; set; } }
我需要编写查询语句,返回包含角色名称列表的UserDTO。目前已实现查询包含Role对象列表的User实体,代码如下:
[HttpGet] [Route("/user")] public async Task<IActionResult> GetUserAsync([FromQuery] Guid userId) { using var dbContext = new DatabaseContext(); var user = await dbContext.Users .Where(user => user.Id == userId) .Include(user => user.Roles) .FirstOrDefaultAsync(); if (group is not null) return Ok(user); else return NotFound(); }
请问如何修改代码以实现需求?
解决方案
有两种常见方式可以实现需求,推荐第一种(直接投影),因为EF Core会自动优化SQL查询,只获取需要的字段,性能更优。
方式一:查询时直接投影为UserDTO
不需要先查询完整的User实体,直接在LINQ查询中构造UserDTO:
[HttpGet] [Route("/user")] public async Task<IActionResult> GetUserAsync([FromQuery] Guid userId) { using var dbContext = new DatabaseContext(); var userDto = await dbContext.Users .Where(u => u.Id == userId) .Select(u => new UserDTO { Id = u.Id, Login = u.Login, Roles = u.Roles.Select(r => r.Name).ToList() }) .FirstOrDefaultAsync(); if (userDto is not null) return Ok(userDto); else return NotFound(); }
说明:这种方式下EF Core会生成只查询Users.Id、Users.Login和Roles.Name的SQL,避免加载不必要的字段(比如Pass)和完整的Role实体,性能更好。
方式二:先查询实体再手动映射为UserDTO
如果需要保留原有的实体查询逻辑,可以在获取到User实体后手动转换为UserDTO:
[HttpGet] [Route("/user")] public async Task<IActionResult> GetUserAsync([FromQuery] Guid userId) { using var dbContext = new DatabaseContext(); var user = await dbContext.Users .Where(u => u.Id == userId) .Include(u => u.Roles) .FirstOrDefaultAsync(); if (user is not null) { var userDto = new UserDTO { Id = user.Id, Login = user.Login, Roles = user.Roles.Select(r => r.Name).ToList() }; return Ok(userDto); } else { return NotFound(); } }
说明:这种方式会加载完整的User实体和关联的Role实体,包括不需要的Pass字段,性能略逊于第一种方式。
另外注意原代码中的if (group is not null)是笔误,两种方案中已修正为if (user is not null)。
内容的提问来源于stack exchange,提问作者TyuJas
相关产品推荐
相关产品推荐

