首页 > 解决方案 > C# 驱动程序聚合查找具有另一个集合的嵌入式数组

问题描述

我有两个 MongoDB 集合、客户和实例:

顾客:

[
  {
    id: 1,
    name: 'Customer Name',
    projects: [
      {
        name: 'Project 1',
        description: 'Project description',
        instances: [10],
      },
      {
        name: 'Project 2',
        description: 'Project description',
        instances: [10, 20],
      },
    ],
  },
]

实例

[
  {
    id: 10,
    operatingSystem: 'Microsoft Windows 2012R2',
    version: '3.1.5',
    product: {
      name: 'Product 1',
      vendor: 'Vendor A',
    },
  },
  {
    id: 20,
    operatingSystem: 'Microsoft Windows 2016',
    version: '4.1.0',
    product: {
      name: 'Product 2',
      vendor: 'Vendor B',
    },
  },
]

我正在尝试使用 C# LINQ 聚合 $lookup 集合 - 没有运气

当每个实例文档与第一个集合 (customers.Projects.Instances) 提供的实例 id 匹配时,预期结果是单个客户(由 id 匹配,即“1”)和实例(第二个集合)详细信息:

{
  id: 1,
  name: 'Customer Name',
  projects: [
    {
      name: 'Project 1',
      description: 'Project description',
      instances: [
        {
          id: 10,
          operatingSystem: 'Microsoft Windows 2012R2',
          version: '3.1.5',
          product: {
            name: 'Product 1',
            vendor: 'Vendor A',
          },
        },
      ],
    },
    {
      name: 'Project 2',
      description: 'Project description',
      instances: [
        {
          id: 10,
          operatingSystem: 'Microsoft Windows 2012R2',
          version: '3.1.5',
          product: {
            name: 'Product 1',
            vendor: 'Vendor A',
          },
        },
        {
          id: 20,
          operatingSystem: 'Microsoft Windows 2016',
          version: '4.1.0',
          product: {
            name: 'Product 2',
            vendor: 'Vendor B',
          },
        },
      ],
    },
  ],
}

感谢您帮助找到合适的 LINQ 查询来匹配/加入两个集合,以最有效的方式匹配预期结果(到数据库的最少往返)

编辑

我试过的代码:

public async ValueTask<dynamic> GetAsync(string customerId)
{
    var customer = await _customers.Aggregate()
        .Match(c => c.Id == customerId)
        .Lookup("instances", "Projects.Instances", "_id", "Results")
        .FirstOrDefaultAsync();
        
    return customer;
}

还有一点要提:

在 Mongo shell 上使用相同的语法会返回结果(不完全符合预期,至少不会抛出错误)

上面的查询返回一个异常:

无法将“MongoDB.Bson.BsonObjectId”类型的对象转换为“MongoDB.Bson.BsonBoolean”类型。

再一次,我更喜欢使用 LINQ 语法,但不幸的是没有成功

更新

我设法运行以下返回匹配实例的代码。现在我只需要找到一种方法将其附加到客户对象,因为这是我真正想要返回的实体:

var results = _customers.AsQueryable()
                .Where(c => c.Id == customerId)
                .SelectMany(i => i.Projects)
                .FirstOrDefault()
                .Instances
                .Join(_instances.AsQueryable(), a => a, b => b.Id, (a, b) => new { Instance = b })
                .ToList();

结果:

[
  {
    instance: {
      id: 10,
      operatingSystem: 'Microsoft Windows 2012R2',
      version: '3.1.5',
      product: {
        name: 'Product 1',
        vendor: 'Vendor A',
      },
    },
  },
  {
    instance: {
      id: 20,
      operatingSystem: 'Microsoft Windows 2016',
      version: '4.1.0',
      product: {
        name: 'Product 2',
        vendor: 'Vendor B',
      },
    },
  },
]

谢谢!

标签: c#mongodb.net-coreaggregatelookup

解决方案


花了几个小时后,下面的代码(使用惊人的 MongoDB 聚合框架)解决了我的问题:

public async ValueTask<dynamic> GetAsync(string customerId)
{
    var unwind = new BsonDocument("$unwind",
        new BsonDocument("path", "$projects"));

    var lookup = new BsonDocument("$lookup",
        new BsonDocument("from", "instances")
        .Add("localField", "projects.instances")
        .Add("foreignField", "_id")
        .Add("as", "projects.instances"));

    var group = new BsonDocument("$group",
        new BsonDocument("_id", "$_id")
        .Add("name", new BsonDocument("$first", "$name"))
        .Add("projects", new BsonDocument("$push", "$$ROOT.projects"))
        );

    return await _customers.Aggregate()
        .Match(c => c.Id == customerId)
        .AppendStage<dynamic>(unwind)
        .AppendStage<dynamic>(lookup)
        .AppendStage<CustomerDetailsModel>(group)
        .FirstOrDefaultAsync();
}

代码解释:

我首先将提供的 customerId 与“客户”集合匹配

然后我 $unwind (flatten) 客户。项目数组,所以我可以轻松地 $lookup 每个匹配的实例

现在我正在使用 $lookup 从实例集合中左外连接匹配的实例。此阶段的结果是 x(匹配实例数)文档,我需要将它们合并到单个文档中:

$group 帮助我实现了这一目标。

我想办法是通过我强烈推荐的官方 MongoDB 大学 ( https://university.mongodb.com/ ) 中的 M121 课程。

如果您考虑过如何改进管道 - 我会很高兴知道


推荐阅读