How to modify a child element in a child array of a document in MongoDB? I'm using the latest C# Driver for MongoDB (C# Driver Version: 2.5, Server Version: 3.2.0) and I've tried solutions from multiple answers on this from SO (duplicate questions also) and from various other sources. Most of the solutions proved to be obsolete.
Below is my sample document:
[{
"_id" : "69c4f77d-05ef-431d-ae17-c076c6173e04",
"title" : "Root Page",
"createdAt" : ISODate("2017-12-21T12:28:00.680+0000"),
"modifiedAt" : ISODate("2017-12-26T07:18:11.165+0000"),
"pages" : [
{
"_id" : "f449dc0b-3d1b-4a59-b622-6a42ce10b147",
"title" : "Test page 1",
"slug" : "test-page-1",
"createdAt" : ISODate("2017-12-21T12:28:00.680+0000"),
"modifiedAt" : ISODate("2017-12-21T12:28:00.680+0000")
},
{
"_id" : "3d1497d7-f74c-4d88-b15c-bf2f9c736374",
"title" : "Test page 2",
"slug" : "test-page-2",
"createdAt" : ISODate("2017-12-25T11:27:55.006+0000"),
"modifiedAt" : ISODate("2017-12-25T11:27:55.006+0000")
},
{
"_id" : "6e827e2a-5a25-4343-b646-885816bb8cc4",
"title" : "Test page 3",
"slug" : "test-page-3",
"createdAt" : ISODate("2017-12-25T11:31:16.516+0000"),
"modifiedAt" : ISODate("2017-12-25T11:31:16.516+0000")
}]
},
...,
...
]
Document Structure: The Root Document contains an array of sub documents, which has it's metadata and a list of pages (array)
Inside a Sub Document - I'm trying to update the title and slug of a child page object inside pages array, but have no luck so far with the latest driver.
I'm using the following filter and update query:
var filter = Builders<SubDocument>.Filter.And(Builders<SubDocument>.Filter.Eq("_id", "xxx"), Builders<SubDocument>.Filter.ElemMatch(x => x.Pages, p => p.Id == "xxx"));
var update = Builders<SubDocument>.Update
.Set("pages.$.title", "changed title")
.Set("pages.$.slug", "changed-title")
.Set("pages.$.modifiedAt", DateTime.UtcNow);
_mongoDb.Documents.UpdateOne(filter, update);
Now - this query returns me an error:
Invalid BSON field name pages.$.title
The references I came along for last few hours are saying to use "$" operator but it throws error in latest driver.
[Edit 1] Here's my models:
public class SubDocument
{
public SubDocument()
{
Id = Guid.NewGuid().ToString();
CreatedAt = DateTime.UtcNow;
ModifiedAt = DateTime.UtcNow;
Pages = new List<Page>();
}
[BsonId]
public string Id { get; set; }
[BsonElement("title")]
public string Title { get; set; }
[BsonElement("createdAt")]
public DateTime CreatedAt { get; set; }
[BsonElement("modifiedAt")]
public DateTime ModifiedAt { get; set; }
[BsonElement("pages")]
public List<Page> Pages { get; set; }
}
public class Page
{
[BsonId]
public string Id { get; set; }
[BsonElement("title")]
public string Title { get; set; }
[BsonElement("slug")]
public string Slug { get; set; }
[BsonElement("createdAt")]
public DateTime CreatedAt { get; set; }
[BsonElement("modifiedAt")]
public DateTime ModifiedAt { get; set; }
}
Anyone? Thanks in advance!
Your code actually does work with mongod v3.4 and driver v2.5.
As suggested by Evk within comments, here is the strongly-typed version of your code, which is refactoring friendly.
var filter = Builders<SubDocument>.Filter
.And(
Builders<SubDocument>.Filter.Eq(d => d.Id, "69c4f77d-05ef-431d-ae17-c076c6173e04"),
Builders<SubDocument>.Filter.ElemMatch(x => x.Pages, p => p.Id == "3d1497d7-f74c-4d88-b15c-bf2f9c736374"));
var update = Builders<SubDocument>.Update
.Set(c => c.Pages[-1].Title, "changed title")
.Set(c => c.Pages[-1].Slug, "changed slug")
.Set(c => c.Pages[-1].ModifiedAt, DateTime.UtcNow);
subDocumentsCollection.UpdateOne(filter, update);
A different approach might consider your subdocument entity as a whole (document oriented databases encourage this approach), thus always updating the entity altogether and avoiding fine-grained updates (that are more likely in case of R-DBMSs). In this case, you might write something like this.
var document = subDocumentsCollection
.Find(d => d.Id == "69c4f77d-05ef-431d-ae17-c076c6173e04")
.Single();
var page = document.Pages.Single(p => p.Id == "3d1497d7-f74c-4d88-b15c-bf2f9c736374");
page.Title = "changed title";
page.Slug = "changed slug";
page.ModifiedAt = DateTime.UtcNow;
subDocumentsCollection.ReplaceOne(d => d.Id == document.Id, document);
This second approach has the following PROs and CONs.
Considering that premature optimization is the root of all evil (or at least most of it) in programming (here), in general I would go for the second approach, falling back to the first one only in case of very strict performance requirements or low network bandwidth.