【问题标题】:Easier way to Update an Array with MongoDB使用 MongoDB 更新数组的更简单方法
【发布时间】:2015-12-03 06:12:06
【问题描述】:

我的猫鼬收藏看起来像:

var followSchema = new Schema({
  facebookId: {type: String, required: true},
  players : [],
  fans: [],
});

当玩家想要关注另一个用户时,我将该用户的 id 添加到 player[] 数组中。

为了做到这一点,我首先查看了玩家的记录:

var myRecord = FollowModel.findOneAndUpdate(
      {facebookId: req.user.facebookId},
      {$setOnInsert: {}},
      {upsert: true, new : true}
    );

以上确保如果播放器不存在,则创建一个。

然后我开始检查 'players[]' 数组:

myRecord.exec(function(err, result) {
  if (err) {
    throw err;
  }

  if (result.players.indexOf(req.body.idToFollow) < 0) {
    result.players.push(req.body.idToFollow);
    result.save();
    res.status(200).send('Player added to Follow List');
  } else {
    res.status(200).send('Already Following this player');
  }
});

我只是想知道是否有更直接和清晰的方式来编写这个查询?

【问题讨论】:

    标签: mongodb mongoose mongodb-query


    【解决方案1】:

    如果您不关心已经创建了跟随,那么您可以在初始查询中使用 $addToSet 表示法:

    var myRecord = FollowModel.findOneAndUpdate(
          {facebookId: req.user.facebookId},
          {$setOnInsert: {},
           $addToSet: {players: req.body.idToFollow}},
          {upsert: true, new : true}
        );
    

    不过,我不确定你在用那个空的 $setOnInsert 做什么。

    【讨论】:

      【解决方案2】:

      如果您“确实关心”在此处添加更多功能(非常建议)并限制更新的开销,您确实不需要返回修改后的文档,或者即使您这样做,总是更好对数组使用原子运算符,例如 $push$addToSet

      “附加功能”还在于,在存储中使用数组时,存储项目的“长度”或“计数”是一种非常明智的做法。这在查询中变得很有用,并且可以通过“索引”有效地访问,而不是其他获取数组“计数”或使用“计数/长度”进行过滤的方法。

      这里更好的构造是使用"Bulk" operations,因为对存在的数组元素的测试与“upserts”的概念不能很好地混合,所以在你想要upsert功能的地方,一个数组测试它在两个操作中会更好。但是由于“批量”操作可以通过“一个请求”发送到服务器,并且您也可以获得“一个响应”,因此这可以减轻这样做的任何实际开销。

      var bulk = FollowModel.collection.initializeOrderedBulkOp();
      
      // Try to add where not found in array
      bulk.find({ 
          "facebookId": req.user.facebookId,
          "players": { "$ne": req.body.idToFollow }
      }).updateOne({
          "$push": { "players": req.body.idToFollow },
          "$inc": { "playerCount": 1 }
      });
      
      // Otherwise create the document if not matched
      bulk.find({
          "facebookId": req.user.facebookId,
      }).upsert().updateOne({
          "$setOnInsert": {
              "players": [req.body.idToFollow]
              "playerCount": 1,
              "fans": [],
              "fanCount": 0
          }
      })
      
      bulk.execute(function(err,result) {
          // Handling in here
      });
      

      这样做的方式是,第一次尝试尝试找到一个文档,其中要添加的数组元素在数组中尚不存在。此处不会尝试“更新插入”,因为如果它与文档不匹配的唯一原因是因为数组元素不存在,您不想创建新文档。但是如果匹配,则将新成员添加到数组中,并且当前的“计数”通过$inc“递增”1,从而保持总计数或长度。

      因此,第二个语句将仅匹配文档,因此使用“upsert”,因为如果未找到关键字段的文档,则将创建该文档。由于所有操作都在$setOnInsert 内部,如果文档已经存在,则不会执行任何操作。

      这只是一个服务器请求和响应,因此包含两个更新操作没有“来回”,这使得这很有效。

      删除一个数组条目基本上是相反的,只是这次如果没有找到就不需要“创建”一个新文档:

      var bulk = FollowModel.collection.initializeOrderedBulkOp();
      
      // Try to remove where found in array
      bulk.find({ 
          "facebookId": req.user.facebookId,
          "players": req.body.idToFollow
      }).updateOne({
           "$pull": { "players": req.body.idToFollow },
           "$inc": { "playerCount": -1 }
      });
      
      bulk.execute(function(err,result) {
          // Handling in here
      });
      

      所以现在你只需要测试数组元素在哪里,然后$pull数组内容中匹配的元素在哪里,同时将“计数”“递减”1以反映删除.

      现在您“可以”在这里使用$addToSet,因为它只会查看数组内容,如果未找到该成员,则会添加该成员,并且出于相同的原因,无需测试使用$pull 时存在的数组元素,因为如果该元素不存在,它将什么也不做。此外,$addToSet 在该上下文中可以直接在“upsert”中使用,只要您不“交叉路径”,因为不允许尝试在 MongoDB 的同一路径上使用多个更新运算符:

      FollowModel.update(
          { "facebookId": req.user.facebookId },
          {
              "$setOnInsert": {
                  "fans": []
              },
              "$addToSet": { "players": req.body.idToFollow }
          },
          { "upsert": true },
          function(err,numAffected) {
              // handling in here
          }
      );
      

      但这将是“错误的”:

      FollowModel.update(
          { "facebookId": req.user.facebookId },
          {
              "$setOnInsert": {
                  "players": [],              // <-- This is a conflict
                  "fans": []
              },
              "$addToSet": { "players": req.body.idToFollow }
          },
          { "upsert": true },
          function(err,numAffected) {
              // handling in here
          }
      );
      

      但是,这样做会失去“计数”功能,因为此类操作只是完成,而不考虑实际存在的内容或是否“添加”或“删除”任何内容。

      保留“计数器”是一件非常好的事情,即使您现在没有立即使用它们,那么在应用程序生命周期的某个阶段您可能会想要它们。因此,理解所涉及的逻辑并立即实施它们是很有意义的。现在付出很小的代价,以后会得到很多好处。


      这里是快速的旁注,因为我通常会尽可能推荐“批量”操作。通过 mongoose 中的 .collection 访问器使用此方法时,您需要注意这些是本机驱动程序方法,因此其行为与“mongoose”方法不同。

      值得注意的是,所有“猫鼬”方法都有一个内置的“检查”功能,以查看与数据库的连接当前是否处于活动状态。如果不是,则操作实际上是“排队”,直到建立连接。使用本机方法,此“检查”不再存在。因此,您要么需要确保已从“首先”执行的“mongoose”方法中存在连接,要么将整个应用程序逻辑包装在“等待”建立连接的构造中:

      mongoose.connection.on("open",function(err) {
          // All app logic or start in here
      });
      

      这样您就可以确定存在连接,并且方法可以返回和使用正确的对象。但是没有连接,“批量”操作会失败。

      【讨论】:

        猜你喜欢
        • 2019-10-16
        • 2014-03-12
        • 2017-08-14
        • 2016-08-16
        • 2011-06-14
        • 2017-10-04
        • 1970-01-01
        • 1970-01-01
        • 2011-04-25
        相关资源
        最近更新 更多