Updating a document in a nested array

Could anyone give me some guidance over the following query?

I am fairly new to Mongo having worked through the MongoDB University courses in October last year, and I started using it on a personal project at the beginning of the team. I am really enjoying it but still feel I have a fair way to go to understand some of these basics. As I’m sure most of your newbies are, my main focus so far has been in SQL data stores.

My subject matter is football tournaments. The schema I have captured below is of a snapshot of a tournament in the planning phase. I will mention that my data access layer is .Net so if anyone has examples relating to this it would be good, but I can translate it over if not.

My query is centred around the phaseGroups Object array. This maps back to a <int, TournamentPhase> dictionary in code and models the Group and Elimination phases of the tournament.

My code initialises the phases and fixtures based on the selected TournamentFormat ruleset during Tournament creation and persists this to a document. The fixtures are generated up front to allow Tournament Planners to arrange them into a schedule prior to the teams signing up.

I’m now in a position where I need to start updating these items.

Looking at a specific example,
phaseGroups[0].phases.teams - I need to access this array to insert and remove teams.

  • What would be the best practise method to insert, remove or update the details of an item in this array?
  • Would it obtain a lock on the whole document while this is happening, or just the array/document I’m updating?
  • I was hoping to bind it to an interface which would allow the user to quickly update the teams associated with a phase. Would I run into issues with the frequency of requests hitting this array?

Any feedback or tips on my approach would also be very welcome.

I’ve managed to put together a solution for this using elemmatch to step through the nested arrays, then using set to update the contents.

For the benefit to all users please share you solution so that we all know.

Yes of course. This is what I settled on. From looking at other solutions I thought that I could use [-1] in place of the positional operator but that didn’t work for me. No doubt I will be revisiting this, as this is very much a happy path scenario so I’ll try some things then.

            MongoClient dbClient = new MongoClient(_connectionSettingsProvider.GetDataConnection());
            var database = dbClient.GetDatabase(_connectionSettingsProvider.GetDatabaseName());

            var collection = database.GetCollection<Models.Tournament>("tournaments");

            var mappedPhase = _tournamentPhaseMapper.CreateNew(tournamentPhaseToSave);

            var tournamentFilter = Builders<Models.Tournament>.Filter.And(
                Builders<Models.Tournament>.Filter.Eq("_id", new ObjectId(tournamentId)),
                Builders<Models.Tournament>.Filter.ElemMatch(t => t.PhaseGroups, g => g.Order == 1),
                Builders<Models.Tournament>.Filter.ElemMatch(t => t.PhaseGroups[0].Phases, g => g.Id == tournamentPhaseToSave.Id)
                );

            var tournamentResult = await collection.Find(tournamentFilter).FirstOrDefaultAsync();

            var update = Builders<Models.Tournament>.Update;
            var setter = update.Set(g => g.PhaseGroups[0].Phases[0], mappedPhase);
            
            await collection.UpdateOneAsync(tournamentFilter, setter);             
1 Like

What I did ended up being incorrect.

I ended up Projecting the collection I needed, finding the indexes of each nested level and using these in my Setter. I haven’t found a better way to update nested Arrays yet but always happy to take suggestions.

MongoClient dbClient = new MongoClient(_connectionSettingsProvider.GetDataConnection());
            var database = dbClient.GetDatabase(_connectionSettingsProvider.GetDatabaseName());

            var collection = database.GetCollection<Models.Tournament>("tournaments");

            var tournamentFilter = Builders<Models.Tournament>.Filter.And(
                Builders<Models.Tournament>.Filter.Eq("_id", new ObjectId(tournamentId)),
                Builders<Models.Tournament>.Filter.ElemMatch(t => t.PhaseGroups, g => g.Order == phaseGroupOrder));

            var projectionDefinition = Builders<Models.Tournament>.Projection.Include(t => t.PhaseGroups).Exclude("_id");

            var tournamentResult = await collection
                .Aggregate()
                .Match(tournamentFilter)
                .Project<Models.Tournament>(projectionDefinition)
                .FirstOrDefaultAsync();
                        
            var phaseGroup = tournamentResult.PhaseGroups.Where(g => g.Order == phaseGroupOrder).FirstOrDefault();
            var groupIndex = tournamentResult.PhaseGroups.ToList().IndexOf(phaseGroup);
            
            var phase = phaseGroup.Phases.FirstOrDefault(p => p.Id == tournamentPhaseToSave.Id);
            var phaseIndex = phaseGroup.Phases.ToList().IndexOf(phase);

            var mappedPhase = _tournamentPhaseMapper.CreateNew(tournamentPhaseToSave);

            var update = Builders<Models.Tournament>.Update;
            var setter = update.Set(g => g.PhaseGroups[groupIndex].Phases[phaseIndex], mappedPhase);
            
            await collection.UpdateOneAsync(tournamentFilter, setter); 

Please publish sample document in text JSON format so that we can cut-n-paste into our systems.

Also what determines which element of phaseGroups you want to update.

And which element of phases you want to update.

I will come back to this. I’ve made some changes to my design and the new design doesn’t quite address this in the same way, but I am interested in best practices.