如何通过单条LINQ查询将ObjectId映射到对应文档(ASP.NET Core+MongoDB)
问题
我正在使用ASP.NET Core和MongoDB开发在线学习应用,现有两个集合的DTO定义如下:
Course集合DTO
public class CourseDTO : DTOBase { [BsonId] public ObjectId Id { get; init; } public required string Name { get; init; } public required string Description { get; init; } public required string ImageUrl { get; init; } public required ObjectId Teacher { get; init; } public ObjectId[] StudentIds { get; init; } = []; }
User集合DTO
public class UserDTO : DTOBase { [BsonId] public ObjectId Id { get; init; } public required string Username { get; init; } public required string PasswordHash { get; init; } public required string PasswordSalt { get; init; } }
我需要实现一个方法,通过课程的ObjectId获取完整课程信息,同时将StudentIds数组映射为对应的用户详情。要求单次数据库请求完成,且保证类型安全。目前已实现课程基础字段和教师信息的查询,但学生部分尚未完成,现有代码如下:
现有查询方法
public async Task<CourseModel> Get(ObjectId id) { var query = from c in repo.Courses.AsQueryable() where c.Id == id join t in repo.Users.AsQueryable() on c.Teacher equals t.Id select new CourseModel { Id = c.Id.ToString(), Name = c.Name, Description = c.Description, ImageUrl = c.ImageUrl, Teacher = new UserModel { Id = t.Id.ToString(), Username = t.Username } // todo: add students }; var course = await query.FirstOrDefaultAsync(); if (course == null) throw new NotFoundException("Course not found"); return course; }
模型定义
public class UserModel { public required string Id { get; init; } public required string Username { get; init; } } public class CourseModel { public required string Id { get; init; } public required string Name { get; init; } public required string Description { get; init; } public required string ImageUrl { get; init; } public required UserModel Teacher { get; init; } public UserModel[] Students { get; init; } = []; }
我可以通过额外查询获取学生数据,但单次查询显然更高效。
解决方案
要实现单次查询关联学生数据,可利用MongoDB LINQ的关联查询能力(对应MongoDB的$lookup聚合阶段),将教师和学生的关联逻辑整合到一个查询中,同时保持类型安全。修改后的代码如下:
public async Task<CourseModel> Get(ObjectId id) { var query = from course in repo.Courses.AsQueryable() where course.Id == id // 关联教师用户 join teacher in repo.Users.AsQueryable() on course.Teacher equals teacher.Id // 关联学生用户:匹配StudentIds中的所有用户ID join student in repo.Users.AsQueryable() on course.StudentIds.Contains(student.Id) equals true // 按课程分组,避免因多个学生导致课程数据重复 group student by new { Course = course, Teacher = teacher } into courseGroup select new CourseModel { Id = courseGroup.Key.Course.Id.ToString(), Name = courseGroup.Key.Course.Name, Description = courseGroup.Key.Course.Description, ImageUrl = courseGroup.Key.Course.ImageUrl, Teacher = new UserModel { Id = courseGroup.Key.Teacher.Id.ToString(), Username = courseGroup.Key.Teacher.Username }, Students = courseGroup.Select(s => new UserModel { Id = s.Id.ToString(), Username = s.Username }).ToArray() }; var course = await query.FirstOrDefaultAsync(); if (course == null) throw new NotFoundException("Course not found"); return course; }
说明
- 单次数据库请求:MongoDB会将上述LINQ查询转换为包含
$match、$lookup和$group的聚合管道,仅向数据库发送一次请求,避免多次查询的性能开销。 - 类型安全:全程使用强类型的DTO和模型,编译阶段即可检查类型错误,无需手动拼接查询字符串。
- 处理空学生列表:如果课程没有学生,
Students会自动初始化为空数组,符合CourseModel的默认值定义。
内容的提问来源于stack exchange,提问作者Thomas Renon
相关产品推荐
相关产品推荐

