C# MongoDB驱动投影子文档数组单字段返回多余结果问题
问题背景
在编写返回子文档数组内指定元素的代码时,需要将MongoDB文档扁平化映射为强类型新文档。
源文档结构如下:
{ "_id" : BinData(3, "7FRf4nbe60ev6XmGKBBW4Q=="), "status" : NumberInt(1), "title":"Central station", "attributes" : [ { "defId" : BinData(3, "QFDtR03NbkqwuhhG76wS8g=="), "value" : "388", "name" : null }, { "defId" : BinData(3, "RE3MT3clb0OdLEkkqhpFOg=="), "value" : "", "name" : null }, { "defId" : BinData(3, "pPgJR50h8kGdDaCcH2o17Q=="), "value" : "Merkez", "name" : null } ] }
期望输出结构如下:
{ "title":"Central Station", "value":"388" }
原有实现代码如下:
using (_dbContext) { var filter = Builders<CustomerModel>.Filter.Eq(q => q.Id, Guid.Parse("30B59585-CBFC-4CD5-A43E-0FDB0AE3167A")) & Builders<CustomerModel>.Filter.ElemMatch(f => f.Attributes, q => q.DefId == Guid.Parse("47ED5040-CD4D-4A6E-B0BA-1846EFAC12F2")); var projection = Builders<CustomerModel>.Projection.Include(f => f.Title).Include("attributes.value"); var document = _dbContext.Collection<CustomerModel>().Find(filter).Project(projection).FirstOrDefault(); if (document == null) return null; return BsonSerializer.Deserialize<TitleAndValueViewModel>(document); }
注:TitleAndValueViewModel包含title和value两个属性。
原有代码实际返回结果:
{ "_id" : CSUUID("30b59585-cbfc-4cd5-a43e-0fdb0ae3167a"), "title" : "388 güvenevler", "attributes" : [ { "value" : "388" }, { "value" : "" }, { "value" : "Merkez " } ] }
核心问题:即使添加了ElemMatch筛选条件,仍然返回了attributes数组下所有元素的value属性,需要基于C# MongoDB驱动的解决方案。
问题原因
查询阶段的ElemMatch仅用于筛选包含符合条件子文档的父文档,不会对返回结果中的数组内容做过滤。原有代码在投影阶段直接指定Include("attributes.value"),会返回数组内所有元素的value字段,因此会带出多余的属性值。
解决方案
以下两种方案均基于官方C# MongoDB驱动实现,可直接返回符合要求的扁平化强类型结果。
方案1:Find语法配合投影过滤
在投影阶段通过表达式直接映射目标模型,自动提取匹配的数组元素值,无需额外反序列化:
using (_dbContext) { var targetCustomerId = Guid.Parse("30B59585-CBFC-4CD5-A43E-0FDB0AE3167A"); var targetAttrDefId = Guid.Parse("47ED5040-CD4D-4A6E-B0BA-1846EFAC12F2"); // 筛选条件:匹配指定ID的客户,且attributes包含目标defId的子项 var filter = Builders<CustomerModel>.Filter.Eq(q => q.Id, targetCustomerId) & Builders<CustomerModel>.Filter.ElemMatch(f => f.Attributes, a => a.DefId == targetAttrDefId); // 投影直接映射为目标ViewModel,自动提取匹配的属性值 var projection = Builders<CustomerModel>.Projection.Expression(customer => new TitleAndValueViewModel { Title = customer.Title, Value = customer.Attributes.First(a => a.DefId == targetAttrDefId).Value }); return _dbContext.Collection<CustomerModel>() .Find(filter) .Project(projection) .FirstOrDefault(); }
方案2:聚合管道实现(兼容性更强)
如果遇到低版本驱动对表达式投影的兼容问题,可使用聚合管道完成数组过滤和字段提取,逻辑更直观:
using (_dbContext) { var targetCustomerId = Guid.Parse("30B59585-CBFC-4CD5-A43E-0FDB0AE3167A"); var targetAttrDefId = Guid.Parse("47ED5040-CD4D-4A6E-B0BA-1846EFAC12F2"); var result = _dbContext.Collection<CustomerModel>().Aggregate() // 匹配目标父文档 .Match(Builders<CustomerModel>.Filter.Eq(q => q.Id, targetCustomerId)) // 投影阶段过滤数组、提取目标字段 .Project(c => new TitleAndValueViewModel { Title = c.Title, Value = c.Attributes.Where(a => a.DefId == targetAttrDefId).Select(a => a.Value).FirstOrDefault() }) .FirstOrDefault(); return result; }
注意事项
- 不需要手动调用
BsonSerializer.Deserialize,驱动的表达式投影会自动完成强类型映射,性能更高。 - 表达式投影默认不会返回
_id字段,无需额外配置排除规则。 - 如果业务上允许attributes数组不存在匹配项,将
First改为FirstOrDefault并增加空值判断即可避免空引用异常。
内容的提问来源于stack exchange,提问作者MonkeyDLuffy
相关产品推荐
相关产品推荐

