MongoDB C# 驱动程序:嵌套查找 - 如何 "join" 嵌套关系?

MongoDB C# Driver: Nested Lookups - How do I "join" nested relations?

我有 3 个 MongoDB 个相互关联的集合:

  1. 公司
  2. 商店:一个公司可以有多个商店
  3. 产品:一个商店可以有多个产品

公司

{
  "_id": { "$oid": "1388445c0000000000000001" },
  "name": "Company A",
  "stores": [
    { "$oid": "1388445c0000000000000011" },
    { "$oid": "1388445c0000000000000012" }
  ]
}

商店

{
  "_id": { "$oid": "1388445c0000000000000011" },
  "name": "Store A",
  "products": [
    { "$oid": "1388445c0000000000000021" },
    { "$oid": "1388445c0000000000000022" },
    { "$oid": "1388445c0000000000000023" }
  ]
}

产品

{
  "_id": { "$oid": "1388445c0000000000000021" },
  "name": "Product A"
}

如果我使用 Lookup 来“连接”前两个集合,则 Store 的 ObjectId 将替换为 Store 集合中的相应对象:

db.GetCollection<BsonDocument>("Company")
    .Aggregate()
    .Lookup("Store", "stores", "_id", "stores")
    .ToList();

{
   "_id": { "$oid": "1388445c0000000000000001" },
   "name": "Company A",
   "stores": [
     {
       "_id": { "$oid": "1388445c0000000000000011" },
       "name": "Store A",
       "products": [
         { "$oid": "1388445c0000000000000021" },
         { "$oid": "1388445c0000000000000022" },
         { "$oid": "1388445c0000000000000023" }
       ]
     },
     ...
   ]
}

但我正在努力“加入”嵌套商店中的产品。

首先我尝试了:

db.GetCollection<BsonDocument>("Company")
    .Aggregate()
    .Lookup("Store", "stores", "_id", "stores")
    .Lookup("Product", "products", "_id", "products")
    .ToList();

但显然,它并没有那么简单。因为字段 products 在公司中不存在,所以没有任何反应。

如果我尝试:

db.GetCollection<BsonDocument>("Company")
    .Aggregate()
    .Lookup("Store", "stores", "_id", "stores")
    .Lookup("Product", "stores.products", "_id", "stores.products")
    .ToList();


{
   "_id": { "$oid": "1388445c0000000000000001" },
   "name": "Company A",
   "stores": {
     "products": [
       {
         "_id": { "$oid": "1388445c0000000000000021" },
         "name": "Product A"
       },
       ...
     ]
   }
}

然后产品被“加入”,但商店的所有其他字段都消失了。此外,字段 stores 不再是一个数组,而是一个对象。

如何使用 MongoDB C# 驱动程序正确设置聚合管道以“连接”3 个集合,以便收到以下结果:

{
   "_id": { "$oid": "1388445c0000000000000001" },
   "name": "Company A",
   "stores": [
     {
       "_id": { "$oid": "1388445c0000000000000011" },
       "name": "Store A",
       "products": [
         {
           "_id": { "$oid": "1388445c0000000000000021" },
           "name": "Product A"
         },
         ...
       ]
     }
   ]
}

旁注: 我正在使用 BsonDocument 而不是具体的 C# 类型。

我认为您应该使用嵌套 $lookup 管道实现如下:

db.Company.aggregate([
  {
    "$lookup": {
      "from": "Store",
      "let": {
        stores: "$stores"
      },
      "pipeline": [
        {
          $match: {
            $expr: {
              $in: [
                "$_id",
                "$$stores"
              ]
            }
          }
        },
        {
          $lookup: {
            "from": "Product",
            let: {
              products: { products: { $ifNull: [ "$products", [] ] } }
            },
            pipeline: [
              {
                $match: {
                  $expr: {
                    $in: [
                      "$_id",
                      "$$products"
                    ]
                  }
                }
              }
            ],
            as: "products"
          }
        }
      ],
      "as": "stores"
    }
  }
])

Sample Mongo Playground

并使用 MongoDB Compass 将查询转换为 BsonDocument

var pipeline = new[]
{
    new BsonDocument("$lookup",
        new BsonDocument
        {
            { "from", "Store" },
            { "let",
                new BsonDocument("stores", "$stores") 
            },
            { "pipeline",
                new BsonArray
                {
                    new BsonDocument("$match",
                        new BsonDocument("$expr",
                            new BsonDocument("$in",
                                new BsonArray
                                {
                                    "$_id",
                                    "$$stores"
                                }
                            )
                        )
                    ),
                    new BsonDocument("$lookup",
                        new BsonDocument
                        {
                            { "from", "Product" },
                            { "let",
                                new BsonDocument("products", 
                                    new BsonDocument("$ifNull", 
                                        new BsonArray 
                                        { 
                                            "$products", 
                                            new BsonArray() 
                                        }
                                    ) 
                                ) 
                            },
                            { "pipeline",
                                new BsonArray
                                {
                                    new BsonDocument("$match",
                                        new BsonDocument("$expr",
                                            new BsonDocument("$in",
                                                new BsonArray
                                                {
                                                    "$_id",
                                                    "$$products"
                                                }
                                            )
                                        )
                                    )
                                } 
                            },
                            { "as", "products" }
                        }
                    )
                } 
            },
            { "as", "stores" }
        }
    )
};

var result = _db.GetCollection<BsonDocument>("Company")
    .Aggregate<BsonDocument>(pipeline)
    .ToList();

Result

感谢@Yong Shun 找到正确答案了

您还可以使用 MongoDB C# 类型构建查询,如下所示:

PipelineStageDefinition<BsonDocument, BsonDocument> stage = PipelineStageDefinitionBuilder.Lookup<BsonDocument, BsonDocument, BsonDocument, IEnumerable<BsonDocument>, BsonDocument>(
  db.GetCollection<BsonDocument>("Store"),
  new BsonDocument("stores", new BsonDocument("$ifNull", new BsonArray { "$stores", new BsonArray() })),
  new PipelineStagePipelineDefinition<BsonDocument, BsonDocument>(new List<PipelineStageDefinition<BsonDocument, BsonDocument>>
  {
    PipelineStageDefinitionBuilder.Match(new BsonDocumentFilterDefinition<BsonDocument>(new BsonDocument("$expr", new BsonDocument("$in", new BsonArray { "$_id", "$$stores" })))),              
    PipelineStageDefinitionBuilder.Lookup<BsonDocument, BsonDocument, BsonDocument, IEnumerable<BsonDocument>, BsonDocument>(
      db.GetCollection<BsonDocument>("Product"),
      new BsonDocument("products", new BsonDocument("$ifNull", new BsonArray { "$products", new BsonArray() })),
      new PipelineStagePipelineDefinition<BsonDocument, BsonDocument>(new List<PipelineStageDefinition<BsonDocument, BsonDocument>>
        {
          PipelineStageDefinitionBuilder.Match(new BsonDocumentFilterDefinition<BsonDocument>(new BsonDocument("$expr", new BsonDocument("$in", new BsonArray { "$_id", "$$products" })))),
      }),
      "products"
    )
  }),
  "stores"
);

List<BsonDocument> result = db.GetCollection<BsonDocument>("Entity").Aggregate().AppendStage(stage).ToList();