Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple update of embedded documents' properties

Tags:

mongodb

I have the following collection:

{ 
"Milestones" : [      
    {       "ActualDate" : null,   
    "Index": 0,
    "Name" : "milestone1",  
    "TargetDate" : ISODate("2011-12-13T22:00:00Z"),         
    "_id" : ObjectId("4ee89ae7e60fc615c42e28d1")},         
    {       "ActualDate" : null,    
    "Index" : 0,    
    "Name" : "milestone2",  
    "TargetDate" : ISODate("2011-12-13T22:00:00Z"),         
    "_id" : ObjectId("4ee89ae7e60fc615c42e28d2") } ]
, 
"Name" : "a", "_id" : ObjectId("4ee89ae7e60fc615c42e28ce") 
}

I want to update definite documents: that have specified _id, List of Milestones._id and ActualDate is null. I dotnet my code looks like:

var query = Query.And(new[] { Query.EQ("_id", ObjectId.Parse(projectId)),
  Query.In("Milestones._id", new BsonArray(values.Select(ObjectId.Parse))), 
 Query.EQ("Milestones.ActualDate", BsonNull.Value) });                

var update = Update.Set("Milestones.$.ActualDate", DateTime.Now.Date);    

Coll.Update(query, update, UpdateFlags.Multi, SafeMode.True);

Or in native code:

db.Projects.update({ "_id" : ObjectId("4ee89ae7e60fc615c42e28ce"), "Milestones._id" : { "$in" : [ObjectId("4ee89ae7e60fc615c42e28d1"), ObjectId("4ee89ae7e60fc615c42e28d2"), ObjectId("4ee8a648e60fc615c41d481e")] }, "Milestones.ActualDate" : null },{ "$set" : { "Milestones.$.ActualDate" : ISODate("2011-12-13T22:00:00Z") } }, false, true)

But only the first item is being updated.

like image 659
1gn1ter Avatar asked Dec 22 '22 04:12

1gn1ter


2 Answers

This is not possible in current moment. Flag multi in update means update of multiple root documents. Positional operator can match only one nested array item. There is such feature in mongodb jira. You can vote up and wait.

Current solution can be only load document, update as you wish and save back or multiple atomic update for each nested array id.

From documentation at mongodb.org:

Currently the $ operator only applies to the first matched item in the query

like image 52
Andrew Orsich Avatar answered Jan 14 '23 19:01

Andrew Orsich


As answered by Andrew Orsich, this is not possible for the moment, at least not as you wish. But loading the document, modifying the array then saving it back will work. The risk is that some other process could modify the array in the meantime, so you would overwrite its changes. To avoid this, you can use optimistic locking, especially if the array is not modified every second.

  1. load the document, including a new attribute: milestones_version
  2. modify the array as needed
  3. save back to mongodb, but now add a query constraint on the milestones_version, and increment it:

    db.Projects.findAndModify({
        query: {
            _id: your_project_id,
            milestones_version: expected_milestones_version
        },
        update: {
            $set: {
                Milestones: modified_milestones
            },
            $inc: {
                milestones_version: 1
            }
        },
        new: 1
    })
    

If another process modified the milestones array (and hence the milestones_version) before we did, then this command will do nothing and simply return null. We just need to reload the document and try again. If the array is not modified every second, then this will be very rare and will not have any impact on performance.

The main problem with this solution is that you have to edit every Project, one by one (no multi: true). You could still write a javascript function and have it run on the server though.

like image 25
MiniQuark Avatar answered Jan 14 '23 19:01

MiniQuark