Reputation: 3742
EDIT: Not looking for the javascript way of doing this. I am looking for the MongoDB C# 2.0 driver way of doing this (I know it might not be possible; but I hope somebody knows a solution).
I am trying to update the value of an item embedded in an array on the primary document in my mongodb.
I am looking for a strongly typed way to do this. I am using the Mongodb c# 2.0 driver
I can do it by popping the element, updating the value, then reinserting. This just doesn't feel right; since I am overwriting what might have been written in the meantime.
Here is what I have tried so far but with no luck:
private readonly IMongoCollection<TempAgenda> _collection;
void Main()
{
var collectionName = "Agenda";
var client = new MongoClient("mongodb://localhost:27017");
var db = client.GetDatabase("Test");
_collection = db.GetCollection<TempAgenda>(collectionName);
UpdateItemTitle(1, 1, "hello");
}
public void UpdateItemTitle(string agendaId, string itemId, string title){
var filter = Builders<TempAgenda>.Filter.Eq(x => x.AgendaId, agendaId);
var update = Builders<TempAgenda>.Update.Set(x => x.Items.Single(p => p.Id.Equals(itemId)).Title, title);
var result = _collection.UpdateOneAsync(filter, update).Result;
}
Upvotes: 29
Views: 32801
Reputation: 1904
Here's the combined working solution of the above answers with .NET 7 and MongoDB.Driver 2.20.0
Use FirstMatchingElement()
or AllElements()
instead of [-1]
AgnendaId
is the parent'sItems
is the NestedArrayUpdate the FirstMatchingElement:
public void UpdateItemTitle(string agendaId, string itemId, string title)
{
var filter = Builders<TempAgenda>.Filter.Where(x => x.AgendaId == agendaId && x.Items.Any(i => i.Id == itemId));
var update = Builders<TempAgenda>.Update.Set(x => x.Items.FirstMatchingElement().Title, title);
_collection.UpdateOneAsync(filter, update);
}
Update AllElements:
public void UpdateItemTitle(string agendaId, string itemId, string title)
{
var filter = Builders<TempAgenda>.Filter.Where(x => x.AgendaId == agendaId && x.Items.Any(i => i.Id == itemId));
var update = Builders<TempAgenda>.Update.Set(x => x.Items.AllElements().Title, title);
_collection.UpdateOneAsync(filter, update);
}
Upvotes: 3
Reputation: 384
In newer drivers ElementAt(-1)
might no longer be supported. I have had code with (-1) that stopped working when going to .NET6
and MongoDB Driver 2.19.0
They have introduced ExtensionMethods instead:
x.A.FirstMatchingElement() => "A.$"
x.A.AllElements() => "A.$[]"
x.A.AllMatchingElements("identifier") => "A.$[identifier]"
Upvotes: 2
Reputation: 19
The correct way to update a Document or sub array is as follows:
var filter = Builders<Declaracion>.Filter.Where(x => x.Id == di && x.RemuneracionMensualActual.RemuneracionActIndustrial.Any(s => s.Id == oid));
var update = Builders<Declaracion>.Update.Set(x => x.RemuneracionMensualActual.RemuneracionActIndustrial.ElementAt(-1).Ingreso, datos.ActividadIndustrial.Ingreso)
.Set(x => x.RemuneracionMensualActual.RemuneracionActIndustrial.ElementAt(-1).RazonSocial, datos.ActividadIndustrial.RazonSocial)
.Set(x => x.RemuneracionMensualActual.RemuneracionActIndustrial.ElementAt(-1).TipoNegocio, datos.ActividadIndustrial.TipoNegocio);
Upvotes: -1
Reputation: 220
Thanks, this was helpful. I have an addition though, I've used the above for arrays, pushing to a nested array and pulling from one. The issue I have found is that if I had an int array (So not an object, just a simple int array) that the PullFilter didn't actually work - "Unable to determine the serialization information" which is strange as it's only an array of ints. What I ended up doing was making it an array of objects with only one int parameter, and it all started to work. Possibly a bug, or perhaps my lack of understanding. Anyway, as I've struggled to find information about pulling and pushing to nested object arrays with the C# 2.0 driver, I thought I should post my findings here, as they use the above syntax.
var filter = Builders<MessageDto>.Filter.Where(x => x._id == entity.ParentID && x.NestedArray.Any(i => i._id == entity._id));
var update = Builders<MessageDto>.Update.PullFilter(x => x.NestedArray.ElementAt(-1).User, Builders<User>.Filter.Eq(f => f.UserID, userID));
Collection<MessageDto>(currentUser).UpdateOneAsync(filter, update);
And also:
var filter = Builders<MessageDto>.Filter.Where(x => x._id == entity.ParentID && x.NestedArray.Any(i => i._id == entity._id));
var update = Builders<MessageDto>.Update.Push(x => x.NestedArray.ElementAt(-1).Users, new User { UserID = userID });
Collection<MessageDto>(currentUser).UpdateOneAsync(filter, update);
Upvotes: 2
Reputation: 1170
Took me a while to figure this out as it doesn't appear to be mentioned in any of the official documentation (or anywhere else). I did however find this on their issue tracker, which explains how to use the positional operator $
with the C# 2.0 driver.
This should do what you want:
public void UpdateItemTitle(string agendaId, string itemId, string title){
var filter = Builders<TempAgenda>.Filter.Where(x => x.AgendaId == agendaId && x.Items.Any(i => i.Id == itemId));
var update = Builders<TempAgenda>.Update.Set(x => x.Items[-1].Title, title);
var result = _collection.UpdateOneAsync(filter, update).Result;
}
Notice that your Item.Single()
clause has been changed to Item.Any()
and moved to the filter definition.
[-1]
or .ElementAt(-1)
is apparently treated specially (actually everything < 0) and will be replaced with the positional operator $
.
The above will be translated to this query:
db.Agenda.update({ AgendaId: 1, Items.Id: 1 }, { $set: { Items.$.Title: "hello" } })
Upvotes: 71