1

I am trying to update a document in Mongo that represents a community with the following scenario.

  • A community has a collection of blocks
  • A block has a collection of floors
  • A floor has a collection of doors
  • A door has a collection of label names

Given a document Id and information about the labels that must be placed into each door, I want to use the MongoDb C# driver v2.10.4 and mongo:latest to update nested lists (several levels). I've reading the documentation, about array filters, but I can't have it working.

I've created a repository from scratch to reproduce the problem, with instructions on the Readme on how to run the integration test and a local MongoDB with docker.

But as a summary, my method groupds the labels so that I can bulk place names on the desired door and then it iterates over these groups and updates on Mongo the specific document setting the desired value inside some levels deep nested object. I couldn't think of a more efficient way.

All the code in the above repo.

The DB document:

public class Community
{
    public Guid Id { get; set; }
    public IEnumerable<Block> Blocks { get; set; } = Enumerable.Empty<Block>();
}

public class Block
{
    public string Name { get; set; } = string.Empty;
    public IEnumerable<Floor> Floors { get; set; } = Enumerable.Empty<Floor>();
}

public class Floor
{
    public string Name { get; set; } = string.Empty;
    public IEnumerable<Door> Doors { get; set; } = Enumerable.Empty<Door>();
}

public class Door
{
    public string Name { get; set; } = string.Empty;
    public IEnumerable<string> LabelNames = Enumerable.Empty<string>();
}

The problematic method with array filters:

public async Task UpdateDoorNames(Guid id, IEnumerable<Label> labels)
{
    var labelsGroupedByHouse =
        labels
            .ToList()
            .GroupBy(x => new { x.BlockId, x.FloorId, x.DoorId })
            .ToList();

    var filter =
        Builders<Community>
            .Filter
            .Where(x => x.Id == id);

    foreach (var house in labelsGroupedByHouse)
    {
        var houseBlockName = house.Key.BlockId;
        var houseFloorName = house.Key.FloorId;
        var houseDoorName = house.Key.DoorId;
        var names = house.Select(x => x.Name).ToList();

        var update =
            Builders<Community>
                .Update
                .Set($"Blocks.$[{houseBlockName}].Floors.$[{houseFloorName}].Doors.$[{houseDoorName}].LabelNames", names);
        await _communities.UpdateOneAsync(filter, update);
    }
}

The exception is

MongoDB.Driver.MongoWriteException with the message "A write operation resulted in an error.
  No array filter found for identifier 'Block 1' in path 'Blocks.$[Block 1].Floors.$[Ground Floor].Doors.$[A].LabelNames'"

Here's a more visual sample on how the nested structure looks like in the database. Notice the value I want to update is the LabelNames, which is an array of string. enter image description here

I appreciate any help to have this working and suggestions on whether it's the right approach assuming that I cannot change the repository's method signature.


SOLUTION RESULT: Thanks for the quick answer @mickl, it works perfectly. Result at this repo's specific point of history exactly as suggested.

1

2 Answers 2

1

The $[{houseBlockName}] expects an identifier which acts as a placeholder and has a corresponding filter defined within arrayfilters (positional filtered). It seems like you're trying to pass the filter value directly which is incorrect.

Your C# code can look like this:

var houseBlockName = house.Key.BlockId;
var houseFloorName = house.Key.FloorId;
var houseDoorName = house.Key.DoorId;
var names = house.Select(x => x.Name).ToList();

var update = Builders<Community>.Update.Set("Blocks.$[block].Floors.$[floor].Doors.$[door].LabelNames", names);

var arrayFilters = new List<ArrayFilterDefinition>();
ArrayFilterDefinition<BsonDocument> blockFilter = new BsonDocument("block.Name", new BsonDocument("$eq", houseBlockName));
ArrayFilterDefinition<BsonDocument> floorFilter = new BsonDocument("floor.Name", new BsonDocument("$eq", houseFloorName));
ArrayFilterDefinition<BsonDocument> doorFilter = new BsonDocument("door.Name", new BsonDocument("$eq", houseDoorName));
arrayFilters.Add(blockFilter);
arrayFilters.Add(floorFilter);
arrayFilters.Add(doorFilter);

var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };

var result = _communities.UpdateOne(filter, update, updateOptions);
Sign up to request clarification or add additional context in comments.

1 Comment

It works perfectly! Added an update to my question just to link to the same repo with your solution.
0
{
    var filterCompany = Builders<CompanyInfo>.Filter.Eq(x => x.Id, Timekeepping.CompanyID);                  
    var update = Builders<CompanyInfo>.Update.Set("LstPersonnel.$[i].Timekeeping.$[j].CheckOutDate", DateTime.UtcNow);
    var arrayFilters = new List<ArrayFilterDefinition>
    {
        new BsonDocumentArrayFilterDefinition<BsonDocument>(new BsonDocument("i.MacAddress",new BsonDocument("$eq", Timekeepping.MacAddress) )),
        new BsonDocumentArrayFilterDefinition<BsonDocument>(new BsonDocument("j.Id", new BsonDocument("$eq", timeKeeping.Id)))
    };
    var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters};
    var updateResult =  await _companys.UpdateOneAsync(filterCompany, update, updateOptions);
    return updateResult.ModifiedCount != 0;   
}

2 Comments

Can you please provide some explanation on what the problem was and how your code is solving it? Also, given you lack function name it's not clear where the code goes exactly/which function from OP it replaces.
I have found the solution to this problem: stackoverflow.com/questions/35040747/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.