基本问题:
如何以原子方式检查文档的子数组以查看条目是否存在,如果存在则更新它,如果不存在则添加它?我还想根据第一个操作的结果自动调整计数器(使用 $inc
)。
完整解释:
我正在尝试编写一种方法来为任意 mongodb 文档提供原子“赞成票”或“反对票”功能。
我希望发生的事情如下:
- 将“votes”和“voteScore”项添加到给定的模式
- “votes”是一个对象数组,由投票类型(赞成或反对)和投票者的 userId 组成
- “voteScore”是所有投票的运行总和(+1 表示赞成票,-1 表示反对票)
我要实现的逻辑是:
- 当用户投赞成票或反对票时,“voteScore”会相应地更新,使用原子
$inc
运算符,并使用该用户的 ID 和投票类型。 - 当用户更改他/她的投票时,voteScore 会自动更新,并且投票会从数组中删除(如果
voteType
为"none"
),或者voteType 已就地更新以反射(reflect)更改。
我该怎么做呢?我假设我将不得不使用以太 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;
}
}
最佳答案
要以原子方式执行此操作,您最好将数组分开以进行“赞成/反对”投票。通过这种方式,您可以 $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 );
这是一个笼统的表示,所以不要太字面意思,但重点是两个数组。
在处理“赞成票”时,您真正需要做的就是确保您没有添加到该“用户”已经存在的“赞成票”数组:
Question.findOneAndUpdate(
{ "_id": question_id, "upVotes": { "$ne": user_id } },
{
"$inc": { "score": 1 },
"$push": { "upVotes": user_id },
"$pull": { "downVotes": user_id }
},
function(err,doc) {
}
);
反过来处理“否决票”:
Question.findOneAndUpdate(
{ "_id": question_id, "downVotes": { "$ne": user_id } },
{
"$inc": { "score": -1 },
"$push": { "downVotes": user_id },
"$pull": { "upVotes": user_id }
},
function(err,doc) {
}
);
对此的逻辑扩展是,您只需从两个数组中“拉出”即可取消所有投票。但是您确实可以对此“聪明”并在您的应用程序中维护“状态”信息,以便您“知道”您是否也在增加或减少“分数”。
所以对于我来说,我会那样做,并且在发送响应时处理“数组”结果,只是过滤掉当前“用户”的状态信息,这样你就可以在取消甚至不发送时做出明智的选择向服务器发出的请求,该用户可能已经投了“赞成票”或“反对票”。
另请注意 $addToSet
如果没有查询 $inc
,这里不是一个选项独立于其他运营商运作。因此,您不想“选择”对更新无效的文档。
关于javascript - 以原子方式更新数组项,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25880978/