Search code examples
mongodbmongodb-.net-driver

Updating multiple complex array elements in MongoDB


I know this has been asked before, but I have yet to find a solution that works efficiently. I am working with the MongoDB C# driver, though this is more of a general question about MongoDB operations.

I have a document structure that looks something like this:

field1: value1
field2: value2
...
users: [ {...user 1 subdocument...}, {...user 2 subdocument...}, ... ]

Some facts:

  • Each user subdocument includes further sub-arrays & subdocuments (so they're fairly complex).
  • The average users array only contains about 5 elements, but in the worst case can surpass 100.
  • Several thousand update operations on multiple users may be conducted per day in this system, each on one document at a time. Larger arrays will receive more frequent updates due to their data size.

I am trying to figure out how to do this efficiently. From what I've heard, you cannot directly set several array elements to new values all at once, so I had to try something else.

I tried using the $pullAll / $AddToSet + $each operations to remove the old array and replace it with a modified one. I am aware that $pullall can remove only the elements that I need as well, but I would like to preserve the order of elements.

The C# code:

try
{
    WriteConcernResult wcr = collection.Update(query,
    Update.Combine(Update.PullAll("users"),
    Update.AddToSetEach("users", newUsers.ToArray())));
}
catch (WriteConcernException wce)
{
    return wce.Message;
}

In this case newUsers is aList<BsonValue>converted to an array. However I am getting the following exception message:

Cannot update 'users' and 'users' at the same time

By the looks of it, I can't have two update statements in use on the same field in the same write operation.

I also tried Update.Set("users", newUsers.ToArray()), but apparently the Set statement doesn't work with arrays, just basic values:

Argument 2: cannot convert from 'MongoDB.Bson.BsonValue[]' to 'MongoDB.Bson.BsonValue'

So then I tried converting that array to a BsonDocument:

Update.Set("users", newUsers.ToArray().ToBsonDocument());

And got this:

An Array value cannot be written to the root level of a BSON document.

I could try replacing the whole document, but that seems like overkill and definitely not very efficient.

So the only thing I can think of now is to run two separate write operations: one to remove the unwanted old users and another to replace them with their newer versions:

WriteConcernResult wcr = collection.Update(query, Update.PullAll("users"));
WriteConcernResult wcr = collection.Update(query, Update.AddToSetEach("users", newUsers.ToArray()));

Is this my best option? Or is there another, better way of doing this?


Solution

  • Your code should work with a minor change:

    Update.Set("users", new BsonArray(newUsers));
    

    BsonArray is a BsonValue, where as an array of documents is not and we don't implicitly convert arrays like we do other primitive values.