3

I have a use case where a view allows the user to update multiple objects and submit at once, how can I make this atomic?

{_id: parent,
 childrenA: [
   {_id: child1, property: "update-me", property2: "leave-alone"},
   {_id: child2, property: "leave-alone", property2: "update-me"}
 ],
 propertyA: "update-me",
 propertyB: "leave-alone", //someone else needs to be able to update this concurrently with this change.
 childrenB:[
   {property: "update-me", property2: "leave-alone"},
   {property: "leave-alone", property2: "update-me"}
 ],

}

property may or may not be another array of nested objects. Is any of this possible programmatically?

EDIT: I need to mention that I cannot reliably update the entire document in some cases, embedded documents can be replaced (address, maybe)

however, I need to aggregate a list of changes e.g. [{"child[Id=child1].FirstName", "newName"},{"child[Id=child3].LastName", "newName"} (not necessarily that syntax, but a change dictionary)

8
  • Update of a single document (including its sub-documents) is atomic in Mongo DB. Commented May 12, 2019 at 8:26
  • @Metheny - I hear that, but how can I specify multiple operations on field level changes to embedded documents living in arrays? I can't replace the whole document as multiple services may be using this document store backed service/ publishing events that this service listens to Commented May 12, 2019 at 16:19
  • how about using the new transaction support starting in version 4.0? Commented May 12, 2019 at 18:48
  • 1
    Maybe this would help: docs.mongodb.com/manual/reference/operator/update/positional And as said any operation on a single document is considered atomic: stackoverflow.com/questions/21798432/… Commented May 13, 2019 at 6:58
  • 1
    Yes, you're right. Another attempt, it seems the following allows updating multiple array items according to a query: docs.mongodb.com/manual/reference/operator/update/… Commented May 13, 2019 at 19:21

2 Answers 2

1
+50

it could be done with one limitation to the best of my knowledge. someone correct me if i'm wrong please. here's the update command:

db.Parents.update(
    {
        "_id": ObjectId("5cf7391a1c86292244c4424e"),
        "ChildrenA": {
            "$elemMatch": {
                "_id": ObjectId("5cf7391a1c86292244c4424c")
            }
        }
    },
    {
        "$set": {
            "ChildrenA.$.Property": "UPDATED",
            "PropertyA": "UPDATED",
            "ChildrenB.0.Property": "UPDATED",
            "ChildrenB.1.Property2": "UPDATED"
        }
    }
)

as you can see you have to use $elemMatch to target a nested child by ID. and from what i can tell you can only have one $elemMatch in a single update command (correct me if i'm wrong).

here's the c# code that generated the above update command. it is using MongoDB.Entities which is a convenience library which i'm the author of.

using MongoDB.Entities;

namespace StackOverflow
{
    public class Program
    {
        public class Parent : Entity
        {
            public ChildA[] ChildrenA { get; set; }
            public string PropertyA { get; set; }
            public string PropertyB { get; set; }
            public ChildB[] ChildrenB { get; set; }
        }

        public class ChildA : Entity
        {
            public string Property { get; set; }
            public string Property2 { get; set; }
        }

        public class ChildB
        {
            public string Property { get; set; }
            public string Property2 { get; set; }
        }

        static void Main(string[] args)
        {
            new DB("test");

            var childA = new ChildA { Property = "update-me", Property2 = "leave-me-alone" };
            var childB = new ChildA { Property = "leave-alone", Property2 = "update-me" };
            childA.Save(); childB.Save();

            var parent = new Parent
            {
                ChildrenA = new[] { childA, childB },
                PropertyA = "update-me",
                PropertyB = "leave-me-alone",
                ChildrenB = new[] {
                new ChildB{ Property = "update-me", Property2 = "leave-me-alone"},
                new ChildB{ Property = "leave-alone", Property2 = "update-me"}
                }
            };
            parent.Save();

            DB.Update<Parent>()
              .Match(
                f => f.Eq(p => p.ID, parent.ID) &
                f.ElemMatch(
                    x => x.ChildrenA, 
                    x => x.ID == childA.ID))
              .Modify(x => x.ChildrenA[-1].Property, "UPDATED")
              .Modify(x => x.PropertyA, "UPDATED")
              .Modify(x => x.ChildrenB[0].Property, "UPDATED")
              .Modify(x => x.ChildrenB[1].Property2, "UPDATED")
              .Execute();
        }
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

So essentially, if I know the location of the individual items, I can skip using $ at all and update using the .index notation?
@Chazt3n yes that's right. if you know the index location of the items you want to update within an array you can batch up all of your changes in a single update command. the issue only arises when you need to target nested items by id or something else. when you need to do that, you gotta issue separate update commands for each item you need to filter on using $elemMatch (to the best of my knowledge).
That sounds like I can reliably pull the document, do all my searching via code, and queue up changes when I do this, will I be able to concurrently send updates to the same document or will this actually do a replace?
@Chazt3n the update command with the $set operator only modifies the specified field. rest of the document is untouched. even my library uses the $set operator internally for updates.
1

You can use the following form version 3.4

db.Collection.findAndModify({
query: {  "_id" : "parent"},
update: { $set: {propertyA: "update-me" , "childrenA.$[childrenAelemnt].property" : "update-me" , "childrenB.$[childrenB2elemnt].property2" : "update-me"
},
arrayFilters: [ {"childrenAelemnt._id": "child1"},{"childrenBelemnt.property2": "leave-alone"} , {"childrenB2elemnt.property": "leave-alone"} ]})

If there is an ID field in childrenB array. it would have been little easier and syntax would have been little consistent

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.