如果您“确实关心”在此处添加更多功能(非常建议)并限制更新的开销,您确实不需要返回修改后的文档,或者即使您这样做,总是更好对数组使用原子运算符,例如 $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
});
这样您就可以确定存在连接,并且方法可以返回和使用正确的对象。但是没有连接,“批量”操作会失败。