使用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#中使用它?

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

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

解决方法

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

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

建立

基本上我们这里有两个系列

实体

{ "_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<Other> 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阶段具有所有相同的参数,$arrayElemAt用First()表示.对于$sort,您只需提供BSON文档或其他有效表达式即可.

另一种是$lookup的新表现形式,带有MongoDB 3.6及以上版本的子管道声明.

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<EntityWithOthers>(lookup)
  .Unwind<EntityWithOthers,EntityWithOther>(p => p.others)
  .sortByDescending(p => p.others.name)
  .ToList();

请求发送到服务器:

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

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

除了其他用途之外,“子管道”的主要任务是减少目标数组$lookup中返回的文档.此外,$unwind在服务器执行时实际上是being “merged”用于$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,other = o.First() }
    )
    .OrderByDescending(p => p.other.name);

请求发送到服务器:

[ 
  { "$match" : { "name" : { "$in" : [ "A","as" : "o"
  } },{ "$project" : {
    "id" : "$_id","other" : { "$arrayElemAt" : [ "$o",{ "$sort" : { "other.name" : -1 } }
]

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

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

请求发送到服务器:

[
  { "$match" : { "name" : { "$in" : [ "A","as" : "o"
  }},"other" : "$o",{ "$unwind" : "$other" },{ "$project" : {
    "id" : "$id","other" : "$other","_id" : 0
  }},{ "$sort" : { "other.name" : -1 } }
]

通常在$lookup之后直接放置$unwind实际上是聚合框架的“optimized pattern”.然而,.NET驱动程序通过在两者之间强制使用$项目而不是使用“as”上的隐含命名来解决这个问题.如果不是这样,当你知道你有“一个”相关结果时,这实际上比$arrayElemAt好.如果你想要$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
            select new { p.id,other = joined.First() }
            into p
            orderby p.other.name descending
            select p;

请求发送到服务器:

[
  { "$match" : { "name" : { "$in" : [ "A","as" : "joined"
  } },"other" : { "$arrayElemAt" : [ "$joined",{ "$sort" : { "other.name" : -1 } }
]

所有这些都非常熟悉,而且实际上只是功能命名.就像使用$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,other = sub_o }
            into p
            orderby p.other.name descending
            select p;

请求发送到服务器:

[ 
  { "$match" : { "name" : { "$in" : [ "A",{ "$unwind" : { 
    "path" : "$joined","preserveNullAndEmptyArrays" : true
  } },"other" : "$joined",{ "$sort" : { "other.name" : -1 } }
]

实际上使用的是“optimized coalescence”表格.翻译仍然坚持添加$project,因为我们需要中间选择以使语句有效.

摘要

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

显示$unwind的选项很大程度上是因为即使使用“单一”匹配,“合并”形式实际上远比使用$arrayElemAt采用“第一”数组元素更加优化.考虑到诸如BSON限制之类的事情,这甚至变得更加重要,其中$lookup目标阵列可能导致父文档超过16MB而无需进一步过滤.这里有另一篇文章Aggregate $lookup Total size of documents in matching pipeline exceeds maximum document size,我实际上讨论了如何通过此时仅使用这些选项或其他可用于Fluent接口的Lookup()语法来避免限制.

相关文章

在要实现单例模式的类当中添加如下代码:实例化的时候:frmC...
1、如果制作圆角窗体,窗体先继承DOTNETBAR的:public parti...
根据网上资料,自己很粗略的实现了一个winform搜索提示,但是...
近期在做DSOFramer这个控件,打算自己弄一个自定义控件来封装...
今天玩了一把WMI,查询了一下电脑的硬件信息,感觉很多代码都...
最近在研究WinWordControl这个控件,因为上级要求在系统里,...