Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by InterstellarGuardian797

.NET: How do I use $graphLookup to retrieve all descendant nodes in MongoDB?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I have a tree structure stored in MongoDB where each node represents an object (e.g., folders, projects, documents, files). Each node has a list of ParentIds referencing its parent nodes. I want to use the $graphLookup aggregation stage to find all children (direct and indirect) of a given node by its _id.

Below is my TreeNode model:

CSHARP
public class TreeNode { [BsonId] [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } public string Name { get; set; } public string ObjectType { get; set; } [BsonRepresentation(BsonType.ObjectId)] public List<string> ParentIds { get; set; } = new List<string>(); }

Here is some test data being inserted:

CSHARP
public async Task InsertNewTestData() { if (await CheckIfDataExistsInCollection()) return; var id1 = ObjectId.GenerateNewId().ToString(); var id2 = ObjectId.GenerateNewId().ToString(); var id3 = ObjectId.GenerateNewId().ToString(); var id4 = ObjectId.GenerateNewId().ToString(); var id5 = ObjectId.GenerateNewId().ToString(); var id6 = ObjectId.GenerateNewId().ToString(); var id7 = ObjectId.GenerateNewId().ToString(); var objects = new List<TreeNode> { new TreeNode { Id = id1, Name = "Workspace", ObjectType = "folder", ParentIds = new List<string>() }, new TreeNode { Id = id2, Name = "Project 1", ObjectType = "project", ParentIds = new List<string> { id1 } }, new TreeNode { Id = id3, Name = "Document 1", ObjectType = "document", ParentIds = new List<string> { id2 } }, new TreeNode { Id = id4, Name = "File 1", ObjectType = "file", ParentIds = new List<string> { id3 } }, new TreeNode { Id = id5, Name = "File 2", ObjectType = "file", ParentIds = new List<string> { id3 } }, new TreeNode { Id = id6, Name = "Document 2", ObjectType = "document", ParentIds = new List<string> { id2 } }, new TreeNode { Id = id7, Name = "Project 2", ObjectType = "project", ParentIds = new List<string> { id1 } }, }; await _context.ObjectsTree.InsertManyAsync(objects); }

I’m trying to retrieve all descendants of a specific node (for example, the node "Project 1") so that I can obtain "Document 1", "File 1", "File 2", and "Document 2". Here’s the $graphLookup pipeline I attempted:

CSHARP
var pipeline = new[] { new BsonDocument("$match", new BsonDocument("_id", new ObjectId("_id"))), new BsonDocument("$graphLookup", new BsonDocument { { "from", "ObjectsTree" }, { "startWith", "$ParentIds" }, { "connectFromField", "_id" }, { "connectToField", "ParentIds" }, { "as", "children" }, { "depthField", "depth" } }) }; var result = await _context.ObjectsTree.Aggregate<BsonDocument>(pipeline).ToListAsync();

The issue appears to be that the ParentIds field is stored as a list of strings, but the aggregation isn’t searching through these correctly. How can I adjust my $graphLookup query so it starts with the node’s _id and correctly finds all descendant nodes?

2

Comments 0

Answers 2

Views 55

2 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

You can solve this by “growing” the tree downward from the provided node. In your case, you need to start with the node’s own _id so that MongoDB finds any node whose ParentIds contains that _id. In other words, your pipeline should use the provided node’s _id as the starting value rather than its ParentIds.

Below is an example of how you can adjust your pipeline:

CSHARP
var pipeline = new[] { // Match the node from which you want to start the lookup. new BsonDocument("$match", new BsonDocument("_id", new ObjectId("YOUR_PROVIDED_NODE_ID"))), new BsonDocument("$graphLookup", new BsonDocument { { "from", "ObjectsTree" }, // Start with the node’s _id (not its ParentIds) { "startWith", "$_id" }, { "connectFromField", "_id" }, // Find nodes whose ParentIds contains the value from connectFromField. { "connectToField", "ParentIds" }, { "as", "children" }, { "depthField", "depth" } }) }; var result = await _context.ObjectsTree.Aggregate<BsonDocument>(pipeline).ToListAsync();

Explanation

  • $match Stage:
    This filters for the node with the provided _id (e.g., "Project 1").

  • $graphLookup Stage:

    • from: The collection in which to search (i.e., "ObjectsTree").
    • startWith: Use the matched node’s _id (i.e., $_id).
    • connectFromField: The source field to use for joining (i.e., the node’s _id).
    • connectToField: The target field in other documents (i.e., the ParentIds array). This finds all nodes where ParentIds contains the source node’s _id.
    • as: The field name that will store the resulting children.
    • depthField: Optionally adds a “depth” field to each resulting document indicating the distance from the start node.

Using this pipeline, if you pass the _id of "Project 1," the lookup will find “Document 1,” “File 1,” “File 2,” and “Document 2” (assuming each node’s ParentIds array properly references its parent).

Note:
Ensure that the types match between the _id field and the elements inside ParentIds. Because you are using the [BsonRepresentation(BsonType.ObjectId)] attribute on the ParentIds, they will be stored as ObjectId in MongoDB. If your types ever get out of sync (for example, if one is a string and the other an ObjectId), consider using an expression operator like $toObjectId or $toString to convert the types.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by NeptunianRanger947 1 month ago

0

If you want to solve this with Linq and .NET classes instead of defining the aggregation stages in JSON, you need to derive a class from TreeNode that contains a property to store the children:

CSHARP
public class TreeNodeWithChildren : TreeNode { public List<TreeNode> Children { get; set; } = new(); }

Afterwards, you can use the fluent interface to run the aggregation pipeline, e.g. (id2 denotes the id you are looking for):

CSHARP
var result = coll .Aggregate() .Match(x => x.Id == id2) .GraphLookup<TreeNode, TreeNode, string, List<string>, string, List<TreeNode>, TreeNodeWithChildren>( coll, x => x.Id, x => x.ParentIds, x => x.Id, x => x.Children) .ToList();

No comments yet.

Discussion

No comments yet.