How to update a field "type-safe" in a deeply nested array with C# MongoDb driver?

Viewed 576

I am trying to update individual fields in a deeply nested array.

    public class Maker
    {
        public int Id { get; set; }
        public List<Vehicle> Vehicles { get; set; }
    }

    public class Vehicle
    {
        public int Id { get; set; }
        public int Price { get; set; }
        public List<Part> Parts { get; set; }
    }

    public class Part
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

Updating a field in a level-1 array works like a charm. Updating a field in a level-2 array fails.

 public void UpdateNestedArrayFields()
    {
        var client = new MongoClient();
        var db = client.GetDatabase("test");
        var makers = db.GetCollection<Maker>("makers");

        // Create a maker and add a vehicle
        var newPart = new Part() { Id = 34, Name = "Wheel" };
        var newVehicle = new Vehicle() { Id = 17, Price = 1000, Parts = new List<Part>() { newPart } };
        var newMaker = new Maker() { Id = 5, Vehicles = new List<Vehicle>() { newVehicle } };

        // Update vehicle's price (WORKS)
        makers.FindOneAndUpdate(
            m => m.Id == newMaker.Id && m.Vehicles.Any(v => v.Id == newVehicle.Id),
            Builders<Maker>.Update.Set(m => m.Vehicles[-1].Price, 2000));

        // Update part's name (**FAILS**)
        makers.FindOneAndUpdate(
            m => m.Id == newMaker.Id && m.Vehicles.Single(v => v.Id == newVehicle.Id).Parts.Any(p=> p.Id == newPart.Id),
            Builders<Maker>.Update.Set(m => m.Vehicles[-1].Parts[-1].Name, "Tire"));
    }       

I understand that multiple positional operators are not supported, so my last line (m.Vehicles[-1].Parts[-1].Name) is kind of crap.

Obviously I am not the only one having this problem and I found different solutions like this or that. However, none of them is type safe. The resulting code is pretty ugly and error prone.

So I was wondering: Is there any way to redesign my code so it is type safe?

I am using MongoDb 4.0.9 and C# driver 2.11.5.

Edit (01-11-2020): For anybody interested, I found this really nice extension by Massimiliano Kraus, which is a bit outdated (2017), but is still working and turned out to be a huge help. Thank you, Massimiliano! MongoDB.DeepUpdater.CSharp

2 Answers

in order to update nested arrays like this, you need to use the array filters feature of mongodb.

the needed mongo query is this:

db.Maker.update(
    {
        "_id": 5
    },
    {
        $set: { 'Vehicles.$[v].Parts.$[p].Name': 'Tyre' }
    },
    {
        arrayFilters: [
            { 'v._id': { $eq: 17 } },
            { 'p._id': { $eq: 34 } }
        ]
    }
)

the c# driver is not capable of generating the above in a strongly-typed fashion. have a look at this article for an alternative semi-typed solution to running advanced queries like the above.

There is an other possible and more typed version of typed nested array update. it not fully typed, but it is closer to what u need.

its also using ArrayFilters but in more typed fashion

this code is coming straight from application that I'm working on.

var filter = Builders<EventModel>.Filter.Eq(s => s.Id, eventId);

var settingsPath = @$"{nameof(EventModel.EventLayout)}.{nameof(EventLayoutSetting.Pages)}.$[page].{nameof(Page.Components)}.$[component].{nameof(Component.Settings)}";
var update = Builders<EventModel>.Update.
                     Set(settingsPath, settings);

var dbResult = await eventsCollection.UpdateOneAsync(filter, update, new UpdateOptions 
            {
                ArrayFilters = new List<ArrayFilterDefinition> 
                {
                    new BsonDocumentArrayFilterDefinition<BsonDocument> (new BsonDocument($"page.{nameof(Page.PageId)}",pageId)),
                    new BsonDocumentArrayFilterDefinition<BsonDocument> (new BsonDocument($"component.{nameof(Component.ComponentId)}",componentId))
                }
            });
Related