【问题标题】:Adding new Members to an Array and Creating/Updating the Referenced Documents将新成员添加到数组并创建/更新引用的文档
【发布时间】:2016-04-14 21:18:47
【问题描述】:

我有以下代码:

function() {

  Server.findOne({
      _id: id
    })
    .populate('_players')
    .exec(function(err, server) {
      if (err) {
        return console.log(err);
      } else if (server) {
        console.log('Server: ' + server.name);
        for (elem in allPlayers) {
          if (allPlayers.hasOwnProperty(elem)) {
            //console.log('DEBUG: allPlayers has Property');
            var player = allPlayers[elem];

            var obj = server._players.find(function(obj) {
              return obj.steam_id === player.steamID;
            });
            if (obj) {
              console.log('|| ' + obj.name + ' was online at: ' + obj.last_online);
              console.log('-->                 update to: ' + new Date());
              obj.last_online = new Date();
              obj.save(function(err, obj) {
                if (err) return console.log(err);
                console.log('DEBUG: existing Player safed: ', obj.name);
              })
            } else {
              var newPlayer = new Player();
              newPlayer._id = new mongoose.Types.ObjectId();
              newPlayer.name = player.name;
              newPlayer.steam_id = player.steamID;
              newPlayer.last_online = new Date();

              newPlayer.save(function(err, newPlayer) {
                if (err) return console.log(err);
                console.log('DEBUG: newPlayer safed: ', newPlayer.name);
              });

              server._players.push(newPlayer);
            }

          } else {
            console.log('There is no Property (allPlayers)');
          }
        }

        server.save(function(err, server) {
          if (err) return console.log(err);
          console.log('DEBUG: server safed: ', server.name);
        })
      } else {
        console.log('There is no such server (updatePlayers)');
      }
    })
}

因此,这样做的目的是获取属于特定服务器的所有现有玩家。如果 allPlayers 数组中有新玩家,则应将其保存在数据库中。如果玩家已经存在,则 last_online 时间刚刚更新。除了一个问题之外,它基本上可以正常工作。如果我尝试从数据库中更新现有玩家,则在保存现有玩家之前,服务器是安全的。所以他们实际上并没有因此而更新,我绝对不明白为什么会这样。顺便说一句,新玩家得到了正确的保护......

希望得到任何建议!

// 调试输出是这样的:

{
    > DEBUG: server safed:  TestServer
    DEBUG: existing Player safed:  How's it going
    DEBUG: existing Player safed:  skullsoul2
    DEBUG: existing Player safed:  Terra
    DEBUG: existing Player safed:  Rundas
    DEBUG: existing Player safed:  [OG]GreyForce
    DEBUG: existing Player safed:  Thyrenos

}

//但应该是这样的

{
    DEBUG: existing Player safed:  How's it going
    DEBUG: existing Player safed:  skullsoul2
    DEBUG: existing Player safed:  Terra
    DEBUG: existing Player safed:  Rundas
    DEBUG: existing Player safed:  [OG]GreyForce
    DEBUG: existing Player safed:  Thyrenos
    > DEBUG: server safed:  TestServer
}

【问题讨论】:

    标签: node.js mongodb mongoose mongodb-query


    【解决方案1】:

    您可以在此处进行很多的改进。需要考虑的一件大事是您在这里处理“异步”方法,这些方法基本上将作用于“回调”或“承诺”,这将在执行完成时得到解决。

    使用 Promise.all 解决“许多”更新

    function updateServerPlayers(id,allPlayers,callback) {
    
      // Get an array of steamID to use in the query to .populate()
      var steamIds = allPlayers.map(function(player) {
        return player.steamID;
      })
    
      /*
        findById is pretty much to the point
    
        Also want to remove any players not present in allPlayers from the array
        response. This makes comparison easy.
      */
    
      Server.findById(id).populate({
        "path": "_players",
        "match": {
            "steam_id": { "$in": steamIds }
        }
      }).exec(function(err,server) {
        if (err) { 
          callback(err);     // should handle the err in the callback response
        } else if (server) {
          // Actually working on the object here
    
          var lastOnline = new Date();
    
          var updatePlayers = _players.map(function(player){ return player.steam_id });
          var allUpdates = allPlayers.filter(function(player) {
            return updatePlayers.indexOf(player.steamID) == -1
          }).map(function(player) {
            /* 
              Make an .update() request for all players not present on the server
              using the "upsert" option to create them if not found
            */
            return Player.update(
              { "steam_id": player.steamID },
              {
                "$setOnInsert": {
                  "name": player.name,
                },
                "$set": { "last_online": lastOnline }
              },
              { "upsert": true }
            )
          })
    
          /* 
            Updating existing server players in one operation
            but appending to the list of allUpdates
          */
          allUpdates.push(
            Player.update(
              { "steam_id": { "$in": updatePlayers } },
              { "$set": { "last_online": lastOnline } },
              { "multi": true }
            )
          );
    
          // This is where we actually resolve things
          Promise.all(allUpdates).then(function(responses) {
    
            // Just get the upsert responses
            var upserted = [];
            responses.filter(function(response) {
              return res.hasOwnProperty("upserted");
            }).forEach(function(response) {
              upserted = upserted.concat(response.upserted);
            });
    
            // Then filter back to just the _id values
            upserted = upserted.map(function(upsert) {
              return upsert._id;
            });
    
            // Then "atomically" add to the server array with $addToSet
            Server.update(
              { "_id": id },
              { "$addToSet": { "_players": upserted } },
              callback
            );
    
          }).catch(callback);   // return any error
    
        } else {
          // Not found should still be an error response
          callback(new Error("server not found"))
        }
      });
    
    }
    

    您正在寻找的基本逻辑要求这些事情:

    1. 获取当前Server 的数据,我们真的只是需要知道allPlayers 的哪些成员已经在_players 数组中。 .populate() 在此处被更改为“仅”返回那些匹配项,并且不返回 allPlayers包含的其他数据。了解 "last_online" 仅更新以及确定可能要创建的 allPlayers 很有用,后者将是列表之间的差异。

    2. 一旦您知道allPlayers 的哪些成员已经在_players 中,哪些不是,您可以将其分解为两类更新。存在:

      • “可能”需要“创建”一个新的Player(如果不存在)的更新。这些是对未找到 "steam_id" 的“upsert”的单独查找。

      • 仅可应用于现有播放器的更新,只需更新"last_online" 值。这些“批量”修改可以通过单个请求对所有列出的文档到$in 进行。

    3. 在“Promises”数组中构造“所有”请求(许多 upsert 和“一个”多重更新),您可以使用 Promise.all() 解决它们并返回修改后的数据。完整的修改响应集很好地告诉您Player 的“更新”文档的_id 值,因此需要将哪些值添加到_players 数组中。

    4. 一旦您知道要添加哪些,您就可以使用$addToSet“安全地”将其应用到Server 文档。这将添加引用每个 Player 的新 _id 成员,“如果”该值不存在。根据逻辑,它不应该在那里,但$addToSet 确保在该文档的_players 数组中可能已经添加了其他内容的情况下不会出现“重复”数据。

    使用 MongoDB 的“批量”操作一次更新“许多”

    function updateServerPlayers(id,allPlayers,callback) {
    
      // Get an array of steamID to use in the query to .populate()
      var steamIds = allPlayers.map(function(player) {
        return player.steamID;
      })
    
      /*
        findById is pretty much to the point
    
        Also want to remove any players not present in allPlayers from the array
        response. This makes comparison easy.
      */
    
      Server.findById(id).populate({
        "path": "_players",
        "match": {
            "steam_id": { "$in": steamIds }
        }
      }).exec(function(err,server) {
        if (err) { 
          callback(err);     // should handle the err in the callback response
        } else if (server) {
          // Actually working on the object here
    
          var bulk = Player.collection.initializeUnorderedBulkOp();
    
          var updatePlayers = _players.map(function(player){ return player.steam_id });
    
          // Build the operations for possible upsert
          allPlayers.filter(function(player) {
            return updatePlayers.indexOf(player.steamID) == -1
          }).forEach(function(player) {
            bulk.find({ "steam_id": player.steamID }).upsert().updateOne({
              "$setOnInsert": { "name": player.name },
              "$currentDate": { "last_online": true }
            });
          });
    
          // Append the operation for 
          bulk.find({ "steam_id": { "$in": updatePlayers } }).update({
            "$currentDate": { "last_online": true }
          });
    
          bulk.execute(function(err,result) {
            if (err) callback(err);
    
            // Get only the "cast" ObjectId values that were upserted
            var upserted = result.getUpSertedIds().map(function(upsert) {
              return mongoose.Types.ObjectId( upsert._id.valueOf() )
            })
    
            // Atomically update the server object with $addToSet
            Server.update(
              { "_id": id },
              { "$addToSet": { "_players": upserted } },
              callback
            );
          });
        } else {
          callback(new Error("server not found"))
        }
      });
    
    }
    

    这使用了非常相同的过程,除了之前的清单创建了一个“承诺数组”以应用于服务器,这种形式“构建”了一个单个请求,它实际上发送了所有立即对服务器进行操作。由于也有一个响应,因此无需等待每个请求完成,因为它开始时只有一个请求。这提供了巨大的性能提升。

    同样的情况是,“单个响应”包含所创建的任何 Player 的所有“更新”_id 值。然后可以将其与相同的 $addToSet 操作一起使用,以将它们附加到 _players 数组中。

    这是一种更好的使用方法,但它确实有一个问题。由于“批量”操作“直接”使用来自底层驱动程序的方法,因此对数据库连接的保护措施与所有 mongoose 模型方法中的保护措施不同。

    在实践中,这意味着“猫鼬方法”(例如模型上的.findById() 调用)必须首先执行,以便底层驱动程序方法引用解析为正确的对象,否则您需要采取其他步骤来确保建立连接。从清单来看,这正是这里的情况,因为 .findById() 已经被调用了。

    因此,尽管 mongoose “隐藏”了故意连​​接的复杂性,但在应用程序初始化周围包含一个“包装器”通常是“好习惯”,以“确保”在应用程序的其余部分运行之前连接数据库。

    如果您需要,最好按如下方式设置此事件处理程序:

    mongoose.connection.on("once",function() {
        // Init application in here
    });
    

    但是对于这段代码的结构,它不是必需的。


    这里的另一件主要事情是,“整个”进程“应该”被包装在一个函数中,该函数将返回 callbackPromise,如图所示。

    其中的所有操作都是“异步”方法,所以这里没有什么可以只是return 的结果,因为“异步”调用需要在实际上给出任何结果之前解决。因此,您应该“始终”以其中一种方式包装使用此类“异步”方法的事物,传入参数,然后在“解析器”函数中处理响应。不是return

    这可能需要一些时间来消化,但是理解这些原理并进一步使用它们是值得的。结果,即使在第一个清单案例中,也能满足您的需求,而且运行速度更快、效率更高。

    另外,通过采用直接的方法,代码本身更加简洁。

    【讨论】:

    • 哇。首先:非常感谢!你当然已经注意到我对 Mongoose/MongoDB 以及 Node.js 都很陌生。正如您已经注意到的那样,这很难消化,我可能需要多读几遍才能完全理解(至少因为我是德国人:D),但这已经帮助了我很多!感谢您的宝贵时间!
    猜你喜欢
    • 1970-01-01
    • 2019-04-28
    • 1970-01-01
    • 2021-11-11
    • 1970-01-01
    • 2022-07-28
    • 1970-01-01
    • 2017-01-19
    • 1970-01-01
    相关资源
    最近更新 更多