如何检查文档的子数组以查看是否存在条目,如果存在则更新它,如果不存在,则以原子方式添加它?我也想原子地调整计数器(使用$inc
),具体取决于第一次操作的结果。
我试图写一个方法来提供原子" upvote"或" downvote"任意mongodb文档的功能。
我希望发生的事情如下:
我试图实施的逻辑是:
$inc
运算符,并使用该用户的ID和投票类型向阵列添加新的投票。voteType
为"none"
),或者投票类型已更新到位以反映变化。我该怎么做呢?我假设我必须使用以太document.update()
或model.findOneAndUpdate()
,因为传统的MongooseArray methods(后跟save()
)不会是原子的
到目前为止,我有以下代码,但它无效:
var mongoose = require('mongoose');
var voteTypes = ['up', 'down'];
var voteSchema = new mongoose.Schema({
userId: { type: Schema.Types.ObjectId, ref: 'User', },
voteType: { type: String, enum: voteTypes }
});
exports.schema = voteSchema;
exports.plugin = function votesPlugin(schema, options) {
schema.add({ voteScore: Number });
schema.add({ votes: [voteSchema] });
schema.methods.atomicVote = function(args, callback) {
var item = this;
var voteType = (args.voteType && args.voteType.toLowerCase()) || 'none';
var incrementScore, query;
switch (voteType) {
case 'none':
incrementScore = -voteTypeValue(voteRemoved.voteType);
query = this.update({ $inc: {voteScore: incrementScore}, $pull: { votes: { userId: args.userId } } });
break;
case 'up':
case 'down':
var newVoteVal = voteTypeValue(args.voteType);
if (/* Vote does not already exist in array */) {
incrementScore = newVoteVal;
query = this.update({$inc: {voteScore: incrementScore}, $addToSet: { userId: args.userId, voteType: args.voteType });
} else {
var vote = /* existing vote */;
if (vote.voteType === args.voteType) return callback(null); // no-op
var prevVoteVal = voteTypeValue(vote.voteType);
incrementScore = (-prevVoteVal) + newVoteVal;
vote.voteType = args.voteType;
// This likely won't work because document.update() doesn't have the right query param
// to infer the index of '$'
query = this.update({$inc: {voteScore: incrementScore}, $set: { 'votes.$': { userId: args.userId, voteType: args.voteType } });
}
break;
default:
return callback(new Error('Invalid or missing "voteType" argument (possible values are "up", "down", or "none").'));
}
query.exec(callback);
};
};
function voteTypeValue(voteType) {
switch (voteType) {
case 'up': return 1;
case 'down': return -1;
default: return 0;
}
}
答案 0 :(得分:3)
要以原子方式执行此操作,最好将数组分离为"向上/向下"票。通过这种方式,您可以同时从每个字段$push
或$pull
。这主要是因为MongoDB无法在文档中的同一字段路径上的单个更新中执行这些操作。试图这样做会导致错误。
简化的架构表示可能如下所示:
var questionSchema = new Schema({
"score": Number,
"upVotes": [{ type: Schema.Types.ObjectId }],
"downVotes": [{ type: Schema.Types.ObjectId }]
]);
module.exports = mongoose.model( 'Question', questionSchema );
它是一般性的表示,所以不要过于字面意思,但重点是两个阵列。
处理" upvote"你真正需要做的就是确保你没有加入" upvotes"数组在哪里"用户"已存在:
Question.findOneAndUpdate(
{ "_id": question_id, "upVotes": { "$ne": user_id } },
{
"$inc": { "score": 1 },
"$push": { "upVotes": user_id },
"$pull": { "downVotes": user_id }
},
function(err,doc) {
}
);
反向处理" downvote":
Question.findOneAndUpdate(
{ "_id": question_id, "downVotes": { "$ne": user_id } },
{
"$inc": { "score": -1 },
"$push": { "downVotes": user_id },
"$pull": { "upVotes": user_id }
},
function(err,doc) {
}
);
这个逻辑扩展就是你只需简单地取消所有选票即可#34;拉动"来自两个阵列。但你确实可以“聪明”#34;关于这一点并保持"状态"您的申请中的信息,以便您了解"如果你正在增加或减少"得分"同样。
所以对我来说,我会这样做,并且还处理"数组"发送响应时的结果只是过滤掉当前用户"对于状态信息,您可以在取消时做出明智的选择,甚至不向服务器发送请求,用户已经在该服务器上投了他们的" upvote"或" downvote"可能是这样。
另请注意,$addToSet
不是一个没有查询的选项,因为$inc
独立于其他运算符运行。所以你不想"选择"一个无法更新的文档。