C# MongoDB如何返回匹配数组属性的对象且无需手动投影
解决方案:过滤嵌套集合并保留原对象结构
方案一:数据库端聚合过滤(无需手动映射属性)
使用MongoDB聚合管道的$filter操作符,结合AddFields阶段替换Subscriptions字段,自动保留其他所有属性,无需手动映射。
代码示例
var partnerId = "123"; var collection = mongoDatabase.GetCollection<DriverDocument>("DriverDocuments"); // 匹配包含符合条件订阅的文档 var matchFilter = Builders<DriverDocument>.Filter.ElemMatch( x => x.Subscriptions, s => s.PartnerId == partnerId ); // 用$filter筛选符合条件的订阅项,替换原Subscriptions字段 var filterStage = new BsonDocument("$addFields", new BsonDocument { { nameof(DriverDocument.Subscriptions), new BsonDocument("$filter", new BsonDocument { { "input", $"${nameof(DriverDocument.Subscriptions)}" }, { "as", "subscription" }, { "cond", new BsonDocument("$eq", new BsonArray { "$$subscription.PartnerId", partnerId }) } }) } }); // 执行聚合查询并映射为强类型对象 var result = await collection.Aggregate() .Match(matchFilter) .AppendStage<DriverDocument>(filterStage) .ToListAsync();
这种方式在数据库端完成过滤,无需手动列出所有需要保留的属性,后续新增DriverDocument的属性时,完全不需要修改查询代码。
方案二:内存端过滤(简单易实现)
如果数据集不大,可以先查询出所有符合条件的文档,再在内存中过滤Subscriptions集合。这种方式代码更简洁,不需要使用任何BsonDocument。
代码示例
var partnerId = "123"; var collection = mongoDatabase.GetCollection<DriverDocument>("DriverDocuments"); // 查询所有包含目标PartnerId订阅的文档 var docs = await collection.Find(Builders<DriverDocument>.Filter.ElemMatch( x => x.Subscriptions, s => s.PartnerId == partnerId )).ToListAsync(); // 内存中过滤每个文档的Subscriptions foreach (var doc in docs) { doc.Subscriptions = doc.Subscriptions.Where(s => s.PartnerId == partnerId).ToList(); }
这种方式的缺点是会加载符合条件文档的完整Subscriptions集合到内存,数据量大时可能影响性能,但胜在实现简单,无需复杂的聚合逻辑。
封装通用聚合过滤方法(进阶)
如果需要频繁处理类似的嵌套集合过滤场景,可以封装通用方法,避免重复编写BsonDocument代码:
public static class MongoExtensions { public static IAggregateFluent<T> FilterNestedArray<T, TItem>( this IAggregateFluent<T> aggregate, Expression<Func<T, IEnumerable<TItem>>> arraySelector, string filterFieldName, object filterValue) { var arrayFieldName = ((MemberExpression)arraySelector.Body).Member.Name; var filterStage = new BsonDocument("$addFields", new BsonDocument { { arrayFieldName, new BsonDocument("$filter", new BsonDocument { { "input", $"${arrayFieldName}" }, { "as", "item" }, { "cond", new BsonDocument("$eq", new BsonArray { $"$$item.{filterFieldName}", filterValue }) } }) } }); return aggregate.AppendStage<T>(filterStage); } }
使用示例
var result = await collection.Aggregate() .Match(matchFilter) .FilterNestedArray<DriverDocument, SubscriptionInfo>( x => x.Subscriptions, nameof(SubscriptionInfo.PartnerId), partnerId) .ToListAsync();
这个通用方法可复用在任何嵌套数组过滤场景,新增属性时完全不需要修改过滤逻辑。
内容的提问来源于stack exchange,提问作者Jon
相关产品推荐
相关产品推荐

