使用C#聚合$ lookup

时间:2018-05-25 13:29:47

标签: c# mongodb aggregation-framework mongodb-.net-driver

我有以下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<BsonDocument>("{ name : value }");
QueryDocument queryDoc = new QueryDocument(document);
MongoCursor toReturn = _connectionCollection.Find(queryDoc);

2 个答案:

答案 0 :(得分:25)

无需解析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;
}

查询

Fluent Interface

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

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

除了其他用途之外,&#34;子管道的主要任务&#34;是减少目标数组$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, 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语句,实际上只是因为功能语句中的简化命名。这确实提出了仅使用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 } }
]

通常在$unwind之后直接放置$lookup实际上是聚合框架的"optimized pattern"。但是,.NET驱动程序通过在$project之间强制使用"as"而不是使用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; 上的隐含命名来解决此问题。如果不是这样的话,当你知道自己有一个&#34;一个&#34;时,这实际上比$arrayElemAt好。相关结果。如果你想要$unwind&#34;合并&#34;,那么你最好使用流畅的界面,或者稍后演示的不同形式。

Querable Natural

[
  { "$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 } }
]

发送到服务器的请求:

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;

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

[ 
  { "$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 } }
]

发送到服务器的请求:

select

实际上使用的是"optimized coalescence"表单。翻译仍然坚持添加$project,因为我们需要中间BsonDocument才能使语句有效。

摘要

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

$unwind的选项主要是因为即使使用&#34;单数&#34;匹配&#34;合并&#34;表单实际上远比使用$arrayElemAt更好地采用&#34;第一个&#34;数组元素。考虑到诸如BSON限制之类的事情,这甚至变得更加重要,其中$lookup目标数组可能导致父文档超过16MB而无需进一步过滤。这里有另一篇帖子Aggregate $lookup Total size of documents in matching pipeline exceeds maximum document size,我实际上讨论了如何通过在此时使用这些选项或其他return语法来使用fluent接口来避免限制。

答案 1 :(得分:0)

这是使用Lookups that span relationships- Django Doc 的方法。如果两个实体处于一对多或多对多关系中,则可以获取反向关系访问权限,而不必手动进行联接,如下所示。 [免责声明:我是图书馆的作者]

using System;
using System.Linq;
using MongoDB.Entities;
using MongoDB.Driver.Linq;

namespace StackOverflow
{
    public class Program
    {
        public class Author : Entity
        {
            public string Name { get; set; }
            public Many<Book> Books { get; set; }

            public Author() => this.InitOneToMany(() => Books);
        }

        public class Book : Entity
        {
            public string Title { get; set; }
        }

        static void Main(string[] args)
        {
            new DB("test");

            var book = new Book { Title = "The Power Of Now" };
            book.Save();

            var author = new Author { Name = "Eckhart Tolle" };
            author.Save();

            author.Books.Add(book);

            //build a query for finding all books that has Power in the title.
            var bookQuery = DB.Queryable<Book>()
                              .Where(b => b.Title.Contains("Power"));

            //find all the authors of books that has a title with Power in them
            var authors = author.Books
                                .ParentsQueryable<Author>(bookQuery); //also can pass in an ID or array of IDs

            //get the result
            var result = authors.ToArray();

            //output the aggregation pipeline
            Console.WriteLine(authors.ToString());


            Console.ReadKey();
        }
    }
}