如何使用C#类聚合MongoDB中的多个集合
解决.NET 8 Blazor Server中MongoDB关联查询的低效问题
问题背景
在.NET 8 Blazor Server应用中使用MongoDB C# Driver,将Team、League、Player三个类分别存储在独立集合中——这些数据需独立更新且可被其他类复用,因此未采用嵌套文档的存储方式。当前通过TeamData类存储关联ID,但希望从数据库获取数据时直接得到完整的Team类实例。现有的循环逐个查询关联数据的方式冗长且低效,需借助AggregateAsync实现关联查询,同时尽量复用现有类结构,减少“魔法字符串”的使用。
解决方案
利用MongoDB聚合框架的$lookup操作在数据库层面完成关联查询,结合C# Driver的强类型API避免硬编码字段名,仅需一次网络请求即可获取完整的Team数据,彻底解决N+1查询的低效问题。
完整实现代码
public async Task<List<Team>> GetAllTeams() { IMongoCollection<TeamData> teamCollection = Database.GetCollection<TeamData>("Teams"); IMongoCollection<League> leagueCollection = Database.GetCollection<League>("Leagues"); IMongoCollection<Player> playerCollection = Database.GetCollection<Player>("Players"); var aggregatePipeline = teamCollection.Aggregate() // 关联League集合,强类型指定字段映射 .Lookup( foreignCollection: leagueCollection, localField: teamData => teamData.LeagueId, foreignField: league => league.Id, @as: (TeamData td) => td.League) // 关联Player1 .Lookup( foreignCollection: playerCollection, localField: teamData => teamData.Player1Id, foreignField: player => player.Id, @as: (TeamData td) => td.Player1) // 关联Player2 .Lookup( foreignCollection: playerCollection, localField: teamData => teamData.Player2Id, foreignField: player => player.Id, @as: (TeamData td) => td.Player2) // 将聚合结果投影为Team实例,取关联数组的第一个元素(ID唯一,仅匹配一条) .Projection(teamData => new Team { Id = teamData.Id, Number = teamData.Number, League = teamData.League.FirstOrDefault(), Player1 = teamData.Player1.FirstOrDefault(), Player2 = teamData.Player2.FirstOrDefault() }); return await aggregatePipeline.ToListAsync(); }
关键说明
- 强类型关联:使用lambda表达式指定
localField和foreignField,完全复用现有类结构,避免了硬编码字段名的“魔法字符串”,提升代码可维护性。 - 数据库端关联:所有关联逻辑在MongoDB服务器执行,仅需一次网络请求,相比原循环查询的多次请求,性能大幅提升。
- 数组处理:
Lookup默认返回匹配文档的数组,由于ID是唯一索引,用FirstOrDefault()即可获取唯一匹配的关联对象,同时处理关联ID无效的空值场景。
可选优化
- 可在聚合管道开头添加
Match阶段,过滤掉关联ID无效的TeamData,减少不必要的关联操作。 - 若需确保关联对象非空,可在投影阶段添加空值判断,或在数据库层面通过约束保证关联ID的有效性。
内容的提问来源于stack exchange,提问作者Jeremy
相关产品推荐
相关产品推荐

