Update on Aggregate in Mongodb

拟墨画扇 提交于 2019-12-22 10:05:53

问题


i am trying to toggle a boolean value inside a object, which is a subdocument, i am having hard time to update a particular object within an array.

Document:

"_id" : ObjectId("54afaabd88694dc019d3b628")
"Invitation" : [ 
    {
        "__v" : 0,
        "ID" : ObjectId("54af6ce091324fd00f97a15f"),
        "__t" : "USER",
        "_id" : ObjectId("54b4ceb50fc380001bea1752"),
        "Accepted" : false
    }, 
    {
        "__v" : 0,
        "ID" : ObjectId("54afac5412f5fdcc007a5c4d"),
        "__t" : "USER",
        "_id" : ObjectId("54b4cebe0fc380001bea1753"),
        "Accepted" : false
    }
],

Controller:

User.aggregate([{$match: {_id: ObjectId(54afaabd88694dc019d3b628)}},{$unwind: '$Invitation'},{$project: {_id: '$_id',Invitation: '$Invitation'}}],function(err,results){

    function updateInvitation(_id){

                var query = {'_id': _id, 'Invitation.ID': ObjectId("54af6ce091324fd00f97a15f")};
                var operator = {$inc: {'Invitation.Accepted': 1}}; 
                User.update(query,operator,{multi:true},function(err,updated){
                    if(err){
                        console.log(err);
                    }
                    console.log('updating'+updated);
                });
            }
            res.jsonp(results);
            updateInvitation(results[0]._id);
});

i tried using $set but it didnt worked out as whole Invitation array got replaced with 'Accepted = 1' How can i toggle 'Accepted' field of the document with particular 'ID'.

Invitation.$.Accepted

Positional operator doesnot apply to field containing array so can't iterate to Accepted field

EDIT:

User.find({_id: req.user._id},'Invitation',function(err,docs){
    if(err){
        console.log(err);
    }
    console.log(docs);
    var results = [];
    async.each(docs,function(doc,err) {
        if(err){
            console.log('error'+ err);
        }
        async.each(docs.Invitation,function(invite,callback) {
console.log('second async');
            User.update(
                { '_id': doc._id, 'Invitation._id': invite._id },
                { '$set': {'Invitation.$.Accepted': !invite.Accepted}},
                function(err,doc) {
                    results.push(doc);
                    console.log('updated'+doc);
                    callback(err);
                }
            );
        });
    },function(err) {
        if (err)
            console.log(err);
        console.log(results);
    });

});

The control is not getting inside second async.each, error is thrown on first async here is the error:

error-function () {
        if (called) throw new Error("Callback was already called.");
        called = true;
        fn.apply(root, arguments);
    }

回答1:


I really don't think that even as a feeder query the aggregation framework is the right operation to use here. All you are doing is "denormalizing" the array as individual documents. There really should be no need. Just fetch the document instead:

var query = {}; // whatever criteria

Users.find(query,"Invitation",function(err,docs) {
    if (err) {
        console.log(err);

    var results = [];        

    async.each(docs,function(doc,callback) {
        async.each(docs.Invitation,function(invite,callback) {
            Users.findOneAndUpdate(
                { "_id": doc._id, "Invitation._id": invite._id },
                { "$set": { "Invitation.$.Accepted": !invite.Accepted } },
                function(err,doc) {
                   results.push( doc );
                   callback(err);
                }
            );
        },callback);
    },function(err) {
        if (err)
            console.log(err);

        console.log(results);
    });    

});

So there is no problem iterating the list of documents in a response for what you are doing, it's just that you also want to iterate the array members as well. The catch is when issuing any kind of .update() that you need to be aware then the asynchronous call is complete.

So I'm using async.each but you probably want async.eachLimit to control the looping. The matching of the element comes from the positional $ operator, corresponding to the matched array element in the query.

It's just JavaScript code, so simply "toggle" the value with !invite.accepted which will inverse it. For additional fun, return the "results" array by pushing the modified document from .findOneAndUpdate().




回答2:


Use the positional update operator for this: http://docs.mongodb.org/manual/reference/operator/update/positional/

User.update(
    {
        "_id": ObjectId("54afaabd88694dc019d3b628"),
        "Invitation": {"$elemMatch": {"ID" : ObjectId("54af6ce091324fd00f97a15f"), "Accepted":false}}
    },
    {
        "$set" : {
            "Invitation.$.Accepted" : true
        }
    },{multi:false, upsert:false, safe:true}, function (err, numAffectedDocuments){
      // TODO
    }
);

Note : you don't need the aggregation step since it actually does nothing.



来源:https://stackoverflow.com/questions/27960568/update-on-aggregate-in-mongodb

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!