总结$查找与C#总结$查找与C#(Aggregate $lookup with C#)

2019-05-12 07:01发布

我有以下的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
            } 
        }
    ]
)

这检索与来自另一个集合匹配对象加入对象的列表。

有谁知道我怎么能在C#中使用LINQ两种或通过使用这个确切的字符串中使用呢?

我试着用下面的代码,但它似乎无法找到该类型QueryDocumentMongoCursor -我认为他们已经被弃用?

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

Answer 1:

没有必要解析JSON。 这里的一切其实是可以用任何LINQ或总结流利的界面直接完成。

只需使用一些示范课,因为这个问题并没有真正提供多少去。

建立

基本上我们有两个集合在这里,是

实体

{ "_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", "B" ] } } },
  { "$lookup" : {
    "from" : "others",
    "let" : { "entity" : "$_id" },
    "pipeline" : [
      { "$match" : { "$expr" : { "$eq" : [ "$$entity", "$entity" ] } } }
    ],
    "as" : "others"
  } },
  { "$unwind" : "$others" },
  { "$sort" : { "others.name" : -1 } }
]

流畅的“生成器”不支持语法直接还,也不LINQ表达式支持$expr运营商,但你仍然可以使用构建BsonDocumentBsonArray或其他有效表达式。 在这里,我们也“式的” $unwind结果以应用$sort使用表达式,而不是BsonDocument前面所示为。

除了其他用途,“子流水线”的首要任务是减少文件的目标数组中返回$lookup 。 另外, $unwind这里供应的实际目的被“合并”到$lookup在服务器上执行的语句,所以这是典型的不只是抓住了结果数组的第一个元素更有效率。

可查询的群组加入

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语句,真的只有在功能语句简化命名的原因。 这确实带来了简单使用的另一种可能性$unwind作为从生产SelectMany()

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 } }
]

通常放置$unwind后直接$lookup实际上是一个“优化模式”为聚合框架。 然而,.NET驱动程序通过迫使确实搞砸中,这一组合$project之间,而不是使用的隐含命名"as" 。 如果不是为,这其实好过$arrayElemAt当你知道你有“一”相关的结果。 如果你想在$unwind “合并”,那么你的时候使用流畅的界面,或以不同的形式为以后更好的证明。

Querable自然

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 } }
]

所有非常熟悉,真的只是到功能命名。 正如使用$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 } }
]

这实际上是用“优化合并”的形式。 译者仍然坚持加入了$project ,因为我们需要的中间select ,以使语句有效。

摘要

因此,有相当多的方式基本上什么基本上是完全相同的结果相同的查询语句到达。 虽然你“可以”解析JSON到BsonDocument形式和饲料这流利的Aggregate()命令,它通常最好使用天然的建设者或LINQ接口,因为它们很容易地映射到相同的语句。

与选项$unwind在很大程度上是示出,因为即使有一个“单数”匹配“聚结”的形式实际上是远更优化然后使用$arrayElemAt取“第一”数组元素。 其中,这甚至成为与事情好像BSON限制的考虑更重要$lookup目标阵列可能会导致父文档,而无需进一步的过滤超过16MB。 还有一个后在这里的匹配管道文件总结$查找总大小超过最大文件大小 ,我其实讨论如何避免这一限制,通过使用这样的选项或其他被击中Lookup()只有在这个时候提供给流畅的界面语法。



文章来源: Aggregate $lookup with C#