1

I have the following structure – for simplicity I abbrev. few properties. The root document (ProjectSchema) as you can see has property rootFolder (FolderSchema) which can have n nested level. For deserialization I am using classes written in C# – ProjectSchema and FolderSchema, mentioned above.

Goal

Deserialize BsonDocument into my custom class ProjectSchema where I need to find specific folder by Id and in that folder insert new FolderSchema

Problem

Could you tell me please how to iterate, respectively find a specific document nested possibly n levels and after all, how can I insert an element into the found one.

Structure

{
    "_id" : ObjectId("5abce10cb02728006f1460fd"),
    "owner_ref" : ObjectId("5ababb7188f6ba0079199dd0"),
    "description" : "",
    "rootFolder" : [ 
        {
            "_id" : ObjectId("5abce10cb02728006f1460fc"),
            "folders" : [ 
                {
                    "_id" : ObjectId("5abce9b5b02728006f1460ff"),
                    "folders" : [ 
                        {
                            "_id" : ObjectId("5abd5775b02728006f146130"),
                            "folders" : [ 
                                {
                                    "_id" : ObjectId("5abd5781b02728006f146131"),
                                    "folders" : [ 
                                        {
                                            "_id" : ObjectId("5abd578ab02728006f146132"),
                                            "folders" : [],
                                            "fileRefs" : [],
                                            "docs" : [],
                                            "name" : "NSubFolder1"
                                        }
                                    ],
                                    "fileRefs" : [],
                                    "docs" : [],
                                    "name" : "SubSubFolder1"
                                }
                            ],
                            "fileRefs" : [],
                            "docs" : [],
                            "name" : "SubFolder1"
                        }
                    ],
                    "fileRefs" : [],
                    "docs" : [],
                    "name" : "Folder1"
                }, 
                {
                    "_id" : ObjectId("5abd576db02728006f14612f"),
                    "folders" : [],
                    "fileRefs" : [],
                    "docs" : [],
                    "name" : "Folder2"
                }
            ],
            "fileRefs" : [],
            "docs" : [ 
                {
                    "_id" : ObjectId("5abce10cb02728006f1460fe"),
                    "name" : "main.tex"
                }
            ],
            "name" : "rootFolder"
        }
    ]
}

Project schema - this holds folders and other attributes. That's my main document.

 public class ProjectSchema
    {
        [BsonId]
        public BsonObjectId ProjectId { get; set; }

        [BsonElement("description")]
        public string Description { get; set; } = "some desc";
    
        [BsonElement("owner_ref")]
        public BsonObjectId OwnerRef { get; set; }

        [BsonElement("rootFolder")]
        public List<FolderSchema> RootFolder { get; set; } = new List<FolderSchema>();
    
        public ProjectSchema()
        {
            ProjectId = new BsonObjectId(ObjectId.GenerateNewId());
        }
    }

Folder schema this holds folders

 public class FolderSchema
    {
        [BsonId]
        public BsonObjectId FolderId { get; set; }

        [BsonElement("name")]
        public string Name { get; set; } = "new folder";

        [BsonElement("docs")]
        public IEnumerable<DocSchema> Docs { get; set; } = new List<DocSchema>();

        [BsonElement("fileRefs")]
        public IEnumerable<FileSchema> FileRefs { get; set; } = new List<FileSchema>();

        [BsonElement("folders")]
        public IEnumerable<FolderSchema> Folders { get; set; } = new List<FolderSchema>();

        public FolderSchema()
        {
            FolderId = new BsonObjectId(ObjectId.GenerateNewId());
        }
    }

My attempt

Unfortunately, this works without success, everything worked without error , but nothing has happened. I assume there is an error in that filter, because Any() targets only first level. I have no idea how to target FolderSchema nested n levels.

Initial call - in this example I am trying to add new folder within folder SubFolder1 named HEUREKA

var projects = database.GetCollection<ProjectSchema>("projects");
var folder = new FolderSchema() { Name = "HEUREKA" };

var filter = Builders<ProjectSchema>.Filter.Where(p => p.ProjectId == new BsonObjectId(new ObjectId("5abce10cb02728006f1460fd"))
                && p.RootFolder.Any(l => l.FolderId == new BsonObjectId(new ObjectId("5abd5775b02728006f146130"))));
var update = Builders<ProjectSchema>.Update.Push(p => p.RootFolder, folder);

await projects.FindOneAndUpdateAsync(filter, update);

#Edit:
It's a good point, to replace whole document with the updated one, the simplest one – and I can admit, it works. However, my documents can be pretty big, So I would rather to update a piece rather then whole document. However, If I pick the option of partial update, I am still not able to update certain part of my document, I do not know how to put it together. So I tried the following one:

For clarification, I know ProjectId ProjectSchema - respectively for which project I want to update a Folder, as well as I know the parent FolderId FolderSchema to which I want to add a new folder.

//**Schema simplification:**

Project Schema
    Folder Schema
       Folder Schema
           Folder Schema
           [n level folder schema]
       Folder Schema
       [n level]

Written filter, to receive Parent folder named "NSubFolder1", or I would use Folder id instead of folder name, because I know it.

var eq = Builders<FolderSchema>.Filter.Eq(f => f.Name, "NSubFolder1");
var emN = Builders<FolderSchema>.Filter.ElemMatch(_ => _.Folders, eq);

Written filter to receive project where I want to add folder.

var eqProj = Builders<ProjectSchema>.Filter.Eq(p => p.ProjectId, "project id here");
var emP = Builders<ProjectSchema>.Filter.ElemMatch(_ => _.ProjectId, eqProj);

How these two filters combine together in order to receive specified FolderSchema under ProjectSchema and then push new FolderSchema to parent array?

1 Answer 1

2

Conceptually what you're trying to do is construct an arbitrarily deep recursive $elemMatch query. I don't think this can be done with a single find/update command with your current database design.

It seems as though you you know the ProjectId of your top level ProjectSchema beforehand - if this is the case, it would be possible to retrieve that document, traverse it, and construct an n-level deep FilterDefinition which would be described like so (though written differently)

var eq = Builders<FolderSchema>.Filter.Eq(f => f.Name, "HEUREKA");
var emN = Builders<FolderSchema>.Filter.ElemMatch(_ => _.Folders, eq);   
...                             
var em2 = Builders<FolderSchema>.Filter.ElemMatch(_ => _.RootFolder, em3);    
var em1 = Builders<FolderSchema>.Filter.ElemMatch(_ => _.RootFolder, em2);
var emRoot = Builders<ProjectSchema>.Filter.ElemMatch(_ => _.RootFolder, em1);

Though at this point, it would seem easiest to simply add your new FolderSchema in the deserialized object and replace the document in the database. Still, you could construct an Update query in much the same way you'd generate the ElemMatch query if you didn't want to replace the entire document.


If you were able to change your schema such that you insert multiple FolderSchema objects into the collection instead of one grand arbitrarily deep ProjectSchema document, it seems you could use the MongoDb Aggregate framework $graphLookup as described at https://docs.mongodb.com/manual/reference/operator/aggregation/graphLookup/ with more examples at https://docs.mongodb.com/manual/tutorial/model-tree-structures/

Without knowing more about your situation, it's hard to say if this would be a better option, or simply an alternative. The ability to use the MongoDB Aggregate framework should definitely be considered a plus.

Sign up to request clarification or add additional context in comments.

1 Comment

Unfortunately, I can not update the schema is strictly given – anyway thanks for graphLookup tip, definitely will use it in future. Your tip about replacing the whole document works perfectly. However, I would like to know a solution how to update only part of document. You know, document could be quite big so do not wasting with resources. Woud you mind if you look at my edited question? I do still struggle with it.

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.