使用C#聚合$ lookup

我有以下MongoDb查询工作:

db.Entity.aggregate( [ { "$match":{"Id": "12345"} }, { "$lookup": { "from": "OtherCollection", "localField": "otherCollectionId", "foreignField": "Id", "as": "ent" } }, { "$project": { "Name": 1, "Date": 1, "OtherObject": { "$arrayElemAt": [ "$ent", 0 ] } } }, { "$sort": { "OtherObject.Profile.Name": 1 } } ] ) 

这将检索与另一个集合中的匹配对象连接的对象列表。

有没有人知道如何使用LINQ或使用这个确切的字符串在C#中使用它?

我尝试使用以下代码,但它似乎无法找到QueryDocumentMongoCursor的类型 – 我认为它们已被弃用?

 BsonDocument document = MongoDB.Bson.Serialization.BsonSerializer.Deserialize("{ name : value }"); QueryDocument queryDoc = new QueryDocument(document); MongoCursor toReturn = _connectionCollection.Find(queryDoc); 

无需解析JSON。 这里的所有内容实际上都可以直接使用LINQ或Aggregate Fluent接口完成。

只是使用一些演示类,因为这个问题并没有真正发挥作用。

建立

基本上我们这里有两个collections品

实体

 { "_id" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "A" } { "_id" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "B" } 

其他人

 { "_id" : ObjectId("5b08cef10a8a7614c70a5712"), "entity" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "Sub-A" } { "_id" : ObjectId("5b08cefd0a8a7614c70a5713"), "entity" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "Sub-B" } 

还有几个绑定它们的类,就像非常基本的例子一样:

 public class Entity { public ObjectId id; public string name { get; set; } } public class Other { public ObjectId id; public ObjectId entity { get; set; } public string name { get; set; } } public class EntityWithOthers { public ObjectId id; public string name { get; set; } public IEnumerable others; } public class EntityWithOther { public ObjectId id; public string name { get; set; } public Other others; } 

查询

流畅的界面

 var listNames = new[] { "A", "B" }; var query = entities.Aggregate() .Match(p => listNames.Contains(p.name)) .Lookup( foreignCollection: others, localField: e => e.id, foreignField: f => f.entity, @as: (EntityWithOthers eo) => eo.others ) .Project(p => new { p.id, p.name, other = p.others.First() } ) .Sort(new BsonDocument("other.name",-1)) .ToList(); 

请求发送到服务器:

 [ { "$match" : { "name" : { "$in" : [ "A", "B" ] } } }, { "$lookup" : { "from" : "others", "localField" : "_id", "foreignField" : "entity", "as" : "others" } }, { "$project" : { "id" : "$_id", "name" : "$name", "other" : { "$arrayElemAt" : [ "$others", 0 ] }, "_id" : 0 } }, { "$sort" : { "other.name" : -1 } } ] 

可能最容易理解,因为流畅的界面基本上与一般的BSON结构相同。 $lookup阶段具有所有相同的参数, $arrayElemAtFirst()表示。 对于$sort您只需提供BSON文档或其他有效表达式即可。

另一种是使用MongoDB 3.6及更高版本的子管道语句的$lookup的新表现forms。

 BsonArray subpipeline = new BsonArray(); subpipeline.Add( new BsonDocument("$match",new BsonDocument( "$expr", new BsonDocument( "$eq", new BsonArray { "$$entity", "$entity" } ) )) ); var lookup = new BsonDocument("$lookup", new BsonDocument("from", "others") .Add("let", new BsonDocument("entity", "$_id")) .Add("pipeline", subpipeline) .Add("as","others") ); var query = entities.Aggregate() .Match(p => listNames.Contains(p.name)) .AppendStage(lookup) .Unwind(p => p.others) .SortByDescending(p => p.others.name) .ToList(); 

请求发送到服务器:

 [ { "$match" : { "name" : { "$in" : [ "A", "B" ] } } }, { "$lookup" : { "from" : "others", "let" : { "entity" : "$_id" }, "pipeline" : [ { "$match" : { "$expr" : { "$eq" : [ "$$entity", "$entity" ] } } } ], "as" : "others" } }, { "$unwind" : "$others" }, { "$sort" : { "others.name" : -1 } } ] 

Fluent“Builder”不直接支持语法,LINQ表达式也不支持$expr运算符,但您仍然可以使用BsonDocumentBsonArray或其他有效表达式构造。 在这里,我们还“键入” $unwind结果,以便使用表达式而不是BsonDocument应用$sort ,如前所示。

除了其他用途之外,“子管道”的主要任务是减少$lookup的目标数组中返回的文档。 此外, $unwind在服务器执行时实际上被“合并”到$lookup语句中,因此这通常比仅仅获取结果数组的第一个元素更有效。

可查询的GroupJoin

 var query = entities.AsQueryable() .Where(p => listNames.Contains(p.name)) .GroupJoin( others.AsQueryable(), p => p.id, o => o.entity, (p, o) => new { p.id, p.name, other = o.First() } ) .OrderByDescending(p => p.other.name); 

请求发送到服务器:

 [ { "$match" : { "name" : { "$in" : [ "A", "B" ] } } }, { "$lookup" : { "from" : "others", "localField" : "_id", "foreignField" : "entity", "as" : "o" } }, { "$project" : { "id" : "$_id", "name" : "$name", "other" : { "$arrayElemAt" : [ "$o", 0 ] }, "_id" : 0 } }, { "$sort" : { "other.name" : -1 } } ] 

这几乎完全相同,但只是使用不同的接口并产生略微不同的BSON语句,实际上只是因为function语句中的简化命名。 这确实提出了另一种简单地使用从SelectMany()生成的$unwind可能性:

 var query = entities.AsQueryable() .Where(p => listNames.Contains(p.name)) .GroupJoin( others.AsQueryable(), p => p.id, o => o.entity, (p, o) => new { p.id, p.name, other = o } ) .SelectMany(p => p.other, (p, other) => new { p.id, p.name, other }) .OrderByDescending(p => p.other.name); 

请求发送到服务器:

 [ { "$match" : { "name" : { "$in" : [ "A", "B" ] } } }, { "$lookup" : { "from" : "others", "localField" : "_id", "foreignField" : "entity", "as" : "o" }}, { "$project" : { "id" : "$_id", "name" : "$name", "other" : "$o", "_id" : 0 } }, { "$unwind" : "$other" }, { "$project" : { "id" : "$id", "name" : "$name", "other" : "$other", "_id" : 0 }}, { "$sort" : { "other.name" : -1 } } ] 

通常在$lookup直接放置$unwind实际上是聚合框架的“优化模式” 。 然而,.NET驱动程序通过在两者之间强制使用$project而不是使用"as"上的隐含命名来解决这个问题。 如果不是这样,当你知道你有“一个”相关结果时,这实际上比$arrayElemAt更好。 如果你想要$unwind “coalescence”,那么你最好使用流畅的界面,或者稍后演示的不同forms。

自然的

 var query = from p in entities.AsQueryable() where listNames.Contains(p.name) join o in others.AsQueryable() on p.id equals o.entity into joined select new { p.id, p.name, other = joined.First() } into p orderby p.other.name descending select p; 

请求发送到服务器:

 [ { "$match" : { "name" : { "$in" : [ "A", "B" ] } } }, { "$lookup" : { "from" : "others", "localField" : "_id", "foreignField" : "entity", "as" : "joined" } }, { "$project" : { "id" : "$_id", "name" : "$name", "other" : { "$arrayElemAt" : [ "$joined", 0 ] }, "_id" : 0 } }, { "$sort" : { "other.name" : -1 } } ] 

所有这些都非常熟悉,而且实际上只是function命名。 就像使用$unwind选项一样:

 var query = from p in entities.AsQueryable() where listNames.Contains(p.name) join o in others.AsQueryable() on p.id equals o.entity into joined from sub_o in joined.DefaultIfEmpty() select new { p.id, p.name, other = sub_o } into p orderby p.other.name descending select p; 

请求发送到服务器:

 [ { "$match" : { "name" : { "$in" : [ "A", "B" ] } } }, { "$lookup" : { "from" : "others", "localField" : "_id", "foreignField" : "entity", "as" : "joined" } }, { "$unwind" : { "path" : "$joined", "preserveNullAndEmptyArrays" : true } }, { "$project" : { "id" : "$_id", "name" : "$name", "other" : "$joined", "_id" : 0 } }, { "$sort" : { "other.name" : -1 } } ] 

实际上是使用“优化合并”forms。 翻译仍然坚持添加$project因为我们需要中间select以使语句有效。

摘要

因此,有很多方法可以基本上达到基本相同的查询语句,结果完全相同。 虽然您“可以”将JSON解析为BsonDocument表单并将其提供给流畅的Aggregate()命令,但通常最好使用自然构建器或LINQ接口,因为它们可以轻松映射到同一语句。

带有$unwind的选项在很大程度上是显示的,因为即使使用“单一”匹配,“聚结”forms实际上也更$arrayElemAt使用$arrayElemAt获取“第一”数组元素。 考虑到像BSON Limit这样的事情,这甚至变得更加重要,其中$lookup目标数组可能导致父文档超过16MB而无需进一步过滤。 这里有关于Aggregate $ lookup的另一篇文章匹配管道中的文档总大小超过了最大文档大小我实际上讨论了如何通过使用此类选项或其他可用于当前流程接口的Lookup()语法来避免限制被命中。