2 / 3
Jun 2024

I’d like to save these classes in two separate collections, with the Categories property containing just a link to the corresponding entry in the category collection.

public class Phonebook { public Guid Id {get; set;} public string Name {get; set;} public List<Category> Categories {get; set;} } public class Category { public Guid Id {get; set;} public string Name {get; set;} }

I’ve previously used LiteDb where I could do this to tell LiteDb that the Categories is a reference.

BsonMapper.Global.Entity<PhoneBook>() .DbRef(x => x.Categories, GetCollectionName<Category>());

and then access it using

database.GetCollection<Phonebook>() .Include(x => x.Categories).FindOne(x => x.Id == myId);

(without the Include it would just return the Id prop of the Categories)

How does that translate to MongoDb and the C# driver?

I know from the sample data model you’d probably ask ‘why not include the in the Phonebook document?’, but the categories are used elsewhere and need to be managed separately (and the same categories need to be available throughout the system).

So, are you saying MongoDb can’t do that?
With the Include (or leaving it out), I can control if I need a join or not - most reads are done without joins., giving me suitable control over when I want to do expensive operations (joins) and when not. LiteDb also only fills the Id of refs unless you ask it to do otherwise which perfectly fits my UI.

I’m struggling quite a bit with the whole ‘it depends’ approach to data modeling. I’ve read some docs, attended a MongoDB event last month where that was a topic, but to me, having a dozen ways to denormalize and one way to properly normalize.

Let me give you some more of my data model

public class Contact

{ public Guid Id {get; set;} public string Name {get; set;} public List<Category> Categories {get; set;} } public class User { public Guid Id {get; set;} public string UserId {get; set;} public List<Phonebook> AccessiblePhonebooks {get; set;} public byte[] Picture {get; set;} }

So, there’s users (of my application) that have access to Phonebooks. Through Phonebook.Categories, users can see what kind of contacts are in a phonebook. That’s basically how they then search the Contact collection (which of course has more data, as has the Phonebook - the Category is basically what you see…) - they are allowed to see a given phonebook, and contacts that are in the phonebook through categorization are then accessible to the users.

Other operations are :

Timer based operations (run every couple of minutes - usually once an hour unless somebody needs more)

  • Get all Contacts that are in a given Phonebook
  • Get all Contacts having a picture that are in a given Phonebook
  • Figure out in which Phonebooks a given contact appears
  • Get basic contact info of all contacts that are in a given Phonebook (just need 4 fields)

Other user initiated operations in order of frequency that touch upon more than a single class - all these happen rarely

Add/Remove Category from Phonebook
Add/Remove Category to/from Phonebook (rather rare)
Add/Update/Delete Category (rather rare)

So, how’d you model that instead of using DbRefs? Note that only Admins of the product are able to create Phonebooks and Categories, and if a category gets deleted, Contact and Phonebooks just lose that category.