如何基于MongoDB中的另一个数组更新嵌入式文档中的字段

时间:2020-06-23 18:58:04

标签: node.js mongodb api

我正在尝试使用nodejs中的mongoose更新MongoDB中的嵌入式文档。该文档经过简化并显示如下(假设friendList中的名称是唯一的):

{
    "_id" : ObjectId("5eb0617f3aec924ff42249cd"),
    "friendList" : [ 
        {
            "name" : "Alex",
            "flag" : false,
        },
        {
            "name" : "Bob",
            "flag" : false,
        },
        {
            "name" : "Caleb",
            "flag" : true,
        },        
        {
            "name" : "Debbie",
            "flag" : false,
        }
    ]
}

我想通过以下方式更新此收藏集:

  1. 接受带有包含friendList
  2. 子集的请求正文的Patch API
  3. 更新嵌套字段flag

例如,如果我要用请求正文从邮递员打补丁:

{
    "friendList":[
        {
            "name":"Alex",
            "flag":true
        },
        {
            "name":"Caleb",
            "flag":false
        },
        {
            "name":"Debbie",
            "flag":false
        }
    ]
}

那么我应该期望我在MongoDB中的文档看起来像这样:

{
    "_id" : ObjectId("5eb0617f3aec924ff42249cd"),
    "friendList":[
        {
            "name":"Alex",
            "flag":true
        },
        {
            "name":"Bob",
            "flag":false
        },
        {
            "name":"Caleb",
            "flag":false
        },
        {
            "name":"Debbie",
            "flag":false
        }
    ]
}

我在nodejs上尝试过的工作是更新整个请求正文:

function updateUser(req){
    User.findOneAndUpdate({'_id':req.params._id},req.body,{new:true});
}

替换了整个friendList数组:

{
    "_id" : ObjectId("5eb0617f3aec924ff42249cd"),
    "friendList":[
        {
            "name":"Alex",
            "flag":true
        },
        {
            "name":"Caleb",
            "flag":false
        },
        {
            "name":"Debbie",
            "flag":false
        }
    ]
}

我也尝试使用$之类的数组运算符:

function updateUser(req){
    User.findOneAndUpdate(
        {'_id':req.params._id},
        {$addToSet:{
            "friendList":{
                $each:req.body.friendList}
            }
        },
        {new:true}
    );
}

这给了我输出:

{
    "_id" : ObjectId("5eb0617f3aec924ff42249cd"),
    "friendList" : [ 
        {
            "name" : "Alex",
            "flag" : false,
        },
        {
            "name" : "Bob",
            "flag" : false,
        },
        {
            "name" : "Caleb",
            "flag" : true,
        },        
        {
            "name" : "Debbie",
            "flag" : false,
        },
        {
            "name" : "Alex",
            "flag" : true,
        },
        {
            "name" : "Caleb",
            "flag" : false,
        },
    ]
}

其中$ addToSet在进行比较以检查数组中是否存在值时会同时考虑nameflag。如果我能够在此比较阶段进行拦截,从而仅检查name字段,则可能会起作用。

我一直在探索$[<identifier>]arrayFilter之类的概念,但似乎无法使其发挥作用。

1 个答案:

答案 0 :(得分:0)

简单的$addToSet不起作用,因为您的数组不是["Alex","Caleb","Debbie"]。您的数组是

[ 
  {name: "Alex", flag: true}, 
  {name: "Caleb", flag: false}, 
  {name: "Debbie", flag: false} 
]

元素{name:"Alex", flag: true}与元素{name: "Alex", flag: false}不同,这就是您的方法失败的原因。我认为您必须使用聚合管道,例如这个:

db.collection.aggregate([
   { $addFields: { newFriends: friendList } },
   {
      $set: {
         friendList: {
            $map: {
               input: "$friendList",
               in: {
                  name: "$$this.name",
                  flag: {
                     $cond: [
                        { $eq: [{ $indexOfArray: ["$newFriends.name", "$$this.name"] }, - 1] },
                        "$$this.flag",
                        { $arrayElemAt: [ "$newFriends.flag", { $indexOfArray: ["$newFriends.name", "$$this.name"] } ] }
                     ]
                  }
               }
            }
         }
      }
   },
   { $unset: "newFriends" }
])

或者如果您想使用索引变量:

db.collection.aggregate([
   { $addFields: { newFriends: friendList } },
   {
      $set: {
         friendList: {
            $map: {
               input: "$friendList",
               in: {
                  $let: {
                     vars: { idx: { $indexOfArray: ["$newFriends.name", "$$this.name"] } },
                     in: {
                        name: "$$this.name",
                        flag: {
                           $cond: [
                              { $eq: ["$$idx", - 1] },
                              "$$this.flag",
                              { $arrayElemAt: ["$newFriends.flag", "$$idx"] }
                           ]
                        }
                     }
                  }
               }
            }
         }
      }
   },
   { $unset: "newFriends" }
])

注意,这只会更新现有名称。新名称未添加到数组,在这方面您的问题尚不清楚。如果您还想添加新元素,请插入

{
   $set: {
      friendList: { $setUnion: ["$friendList", "$newFriends"] }
   }
},

就在{ $unset: "newFriends" }

之前

可以在更新中使用聚合管道:

User.findOneAndUpdate(
    {'_id':req.params._id},
    [
      { $addFields: { newFriends: req.body.friendList } },
      {
         $set: { ...
      }
    ]
);