Reputation: 1651
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)
Upvotes: 3
Views: 2153
Reputation: 133
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
Upvotes: 1
Reputation: 5669
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();
}
}
}
Upvotes: 1