2 / 2
Jul 2024

I’m working with nested data (3~4 levels of nesting) and trying to find a way to maintain parent → child and child → parent references without a lot of overhead. My data is Order info, Order has a list of Groups, Group has a list of Items, and so on. For simplicity, I’ll focus on Order <-> Item.

I’ve given Item an Order field, which just point to its parent Order and is populated with data when an Order is retrieved:

Order RetrieveOrder(string orderId) { var order = Orders.Find(Builders<Order>.Filter.Eq(order => order Id, orderId).SingleOrDefault(); foreach (item in order.Items) item.Order = order; return order; } class Order { [BsonId] [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } List<Item> Items { get; set; } } class Item { [BsonId] [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } [BsonIgnore, JsonIgnore] public Order Order { get; set; } }

The way I’m writing updates is by making changes to an item, then calling ReplaceOne() with the item’s order (and checking ModifiedCount after the db operation) e.g:

item.ChangeStatus(); var Orders = db.GetCollection<Order>("Orders"); var filter = Builders<Order>.Filter.Eq(order => order.Id, item.Order.Id) var result = await Orders.ReplaceOneAsync(filter, item.Order) if (result.ModifiedCount > 0) //do other stuff

First question, is it possible to set up something like an interceptor which will fill out those parent references when an Order is retrieved from the db? I’ve explored ClassMaps and custom serializers, but haven’t found a way to get it working yet. Ideally, after an Order has finished being deserialized, my interceptor/postprocessor would go in and set parent references for Items (and eventually Groups and other nested objects). If it’s not possible to do this from the Order level, can I do in at the Item level? When I deserialize an Item, can I set it’s Order?

This gets me partway, but when an Order is sent between client/server the Items lose their parent references (because they’re marked BsonIgnore, JsonIgnore). I can add string OrderId to Item so I can keep those references after de/serialization, but I’m not sure the best approach to get the relevant Order again at the other end. I can do a “brute force” approach and just do a db query to get my Order then find the relevant Item in that order, but that’s a lot of overhead for a pretty frequent operation. Is there a better way? Am I looking in completely the wrong direction?

Short version:

  1. Can I set up an interceptor/postprocessor which will set Item.Order? Can this be done directly from the Order or does it have to be per-Item?
  2. Is there a better way to manage this sort of child → parent reference in my application?

Any help greatly appreciated!

Update: I’ve changed my design so Item has an OrderId and I’m explicitly searching for an Order by ID. It’s only a small amount of extra overhead and not required for all operations. A big part of my problem was losing details when an Order is sent across the wire between server and client, which isn’t even a Mongo issue - it’s a de/serialization issue.

The only change in the code sample above is changing Item.Order to Item.OrderId and removing [BsonIgnore]:

Order RetrieveOrder(string orderId) { var order = Orders.Find(Builders<Order>.Filter.Eq(order => order Id, orderId).SingleOrDefault(); foreach (item in order.Items) item.OrderId = order.Id; return order; } class Item { [BsonId] [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } public string OrderId { get; set; } }