首页 > 解决方案 > 根据 id 更新两层嵌套对象

问题描述

我的Mother模型中有这个结构(这是一个固定结构,我只是在这 3 个数组级别上推送卡片或更新它们):

{
  cards: {
    starter: [],
    intermediate: [],
    advanced: [ {Object}, {Object}, {Object} ]
  },
}

上面的Objects内部cards.advanced数组如下:

{
  cards: [
    { // this is a single card object 
      title: 'this is a card',
      id: 'main-2-1' // this is unique id only in advanced array, we may have exact id for a card in starter or in intermediate array
    }
  ],
  unit: 2 // this is the unit
}

假设我可以访问这样的Mother模型:

const motherModel = await db.Mother.findOne({}); // this retrieves all data in the Model

我们如何根据卡片对象及其所属更新id卡片对象并将level整个卡片对象替换为newCard

const level = 'advanced'; // the level of the card we want to search for

const cardID = 'main-2-1'; // the exact id of the card we want to be replaced

const cardUnit = cardID.split('-')[1]; // I can calculate this as the unit in which the card exist inside

const newCard = { // new card to be replaced 
    title: 'this is our new updated card',
    id: 'main-2-1' 
}

我试过这个没有运气:

const updated =  await db.Mother.update(
        { ["cards." + level + ".unit"]: cardUnit },            
        { ["cards." + level + ".$.cards"]: newCard }
    )

我也试过这个,但它并没有改变模型中的任何东西:

async function updateMotherCard(card, level) {

    const cardID = card.id;
    const cardUnit = cardID.split('-')[1];

    const motherModel = await db.Mother.findOne({});
    const motherLevel = motherModel.cards[level];

    const selectedUnit = motherLevel.find(e => e.unit == cardUnit);

    let selectedCard = selectedUnit.cards.find(e => e.id == cardID);

    selectedCard = card;

    const updated = await motherModel.save();

    console.log(updated);

}

标签: javascriptnode.jsmongodbmongoose

解决方案


您实际上可以使用update方法解决您的问题,但如果您使用的是 MongoDB 4.2 或更高版本,则必须以不同的方式进行。第二个参数可以是$set您要执行的操作或aggregation管道。使用后者,您可以更自由地塑造数据。这是您解决问题的方法,我将在以下情况下进行分解:

db.collection.update({
  "cards.advanced.unit": 2
},
[
  {
    $set: {
      "cards.advanced": {
        $map: {
          input: "$cards.advanced",
          as: "adv",
          in: {
            cards: {
              $map: {
                input: "$$adv.cards",
                as: "advcard",
                in: {
                  $cond: [
                    {
                      $eq: [
                        "$$advcard.id",
                        "main-2-1"
                      ]
                    },
                    {
                      title: "this is a NEW updated card",
                      id: "$$advcard.id"
                    },
                    "$$advcard"
                  ]
                }
              }
            },
            unit: "$$adv.unit"
          }
        }
      }
    }
  }
],
{
  new: true,
  
});

首先使用传递三个参数的更新方法:

  • 过滤查询
  • 聚合管道
  • 选项。这里我只是用来new: true返回更新后的文档,方便测试。

这是结构:

db.collection.update({
  "cards.advanced.unit": 2
},
[
  // Pipeline
],
{
  new: true,
});

在管道内部,我们只需要一个阶段,用我们将创建的数组$set替换属性。advanced

...
[
  {
    $set: {
      "cards.advanced": {
        // Our first map
      } 
    }
  }
]
...

我们首先映射advanced数组以便能够映射嵌套的卡片数组:

...
[
  {
    $set: {
      "cards.advanced": {
        $map: {
          input: "$cards.advanced",
          as: "adv",
          in: {
            // Here we will map the nested array
          }
        }     
      } 
    }
  }
]
...

我们使用我们在第一个映射中声明的变量,其中包含正在映射的高级数组当前项 ( adv) 来访问和映射嵌套的“卡片”数组 ( $$adv.cards):

...
[
  {
    $set: {
      "cards.advanced": {
        $map: {
          input: "$cards.advanced",
          as: "adv",
          in: {
            cards: {
              $map: {
                input: "$$adv.cards",
                as: "advcard",
                in: {
                // We place our condition to check for the chosen card here
                }
              }
            },
            unit: "$$adv.unit",
          }
        }     
      } 
    }
  }
]
...

最后,我们检查当前卡 id 是否等于正在搜索的 id,$eq: [ "$$advcard.id", "main-2-1" ]如果匹配则返回新卡或当前卡:

...
{
  $cond: [
    {
      $eq: [
        "$$advcard.id",
        "main-2-1"
      ]
    },
    {
      title: "this is a NEW updated card",
      id: "$$advcard"
    },
    "$$advcard"
  ]
}

...

这是所描述内容的工作示例: https ://mongoplayground.net/p/xivZGNeD8ng


推荐阅读