Managing multiple MongoDB C# class maps – Part 1

I’ve used MongoDB with several net projects over the last few years, and one thing that has in the past frustrated me is the way BsonMapping  is set up.

This once per app setup can get pretty long, complex and hard to follow.

As per the documentation this, on the face of it,  it’s fairly easy:

BsonClassMap.RegisterClassMap()

Which will automap MyClass.
Note – There is no actual need for this line of code.

You can either create this class map yourself or simply allow the class map to be created automatically when first needed (called “automapping”). If you want to customise the mapping of your class, you can provide a lambda to the generic RegisterClassMap method:

BsonClassMap.RegisterClassMap(cm => {
    cm.MapProperty(c => c.SomeProperty);
});

… with many options and arguments on MapProperty, which I won’t go into in this post. But what if we have many classes in our domain? Well, with the current set up, we’d have to call RegisterClassMap  once per domain somehow (Global.asax, App start, etc.. are common places)

I prefer to keep things separate, and think that the way fluent nHibernate ClassMap handles this is great.
I wanted something similar for my MongoDB projects, so I use the following pattern and set of classes.

Firstly, we create an abstract class to act as a base class for our class maps. The constructor checks if we already have a class map of that type (passed in by generic) If not, we call our abstract Map void by delegate.

    public abstract class MongoDbClassMap<T]]
    {
        protected MongoDbClassMap()
        {
            if (!BsonClassMap.IsClassMapRegistered(typeof(T)))
                BsonClassMap.RegisterClassMap<T]](Map);
        }

        public abstract void Map(BsonClassMap<T]] cm);
    }

Then create a class, inheriting from MongoDbClassMap, like this:

    
public class MyClassClassMap : MongoDbClassMap
{
    public override void Map(BsonClassMap cm)
    {
        cm.AutoMap();

        cm.MapIdField(x => x.Id).SetIdGenerator(new StringObjectIdGenerator());

        cm.MapProperty(x => x.SomeProperty)
            .SetElementName("sp"); // will set the element name to "sp" in the stored documents

        //unmap the property.. now we won't save it
        cm.UnmapProperty(x => x.SomeOtherProperty);
    }
}

You would have one of these per class you want to map, keeping things nice and separate. Now, all you need to do, is new up an instance of each of your *ClassMap classes.
There are several ways to do this.
One, is to do it manually.
I don’t recommend this; You WILL forget to add one to the list of new *ClassMap() calls, I promise!

The way I handle this is with reflection. Since we’re only doing this once per app domain (on startup) this fairly expensive operation is excusable.

 
//How you get this assembly is up to you
//It could be this assembly
//Or it could be a collection of assemblies, in which case, wrap this block in a foreach and iterate
var assembly = Assembly.GetAssembly(typeof (MyClassClassMap));

//get all types that have our MongoDbClassMap as their base class
var classMaps = assembly
    .GetTypes()
    .Where(t => t.BaseType != null && t.BaseType.IsGenericType &&
                t.BaseType.GetGenericTypeDefinition() == typeof (MongoDbClassMap<>));

//automate the new *ClassMap()
foreach (var classMap in classMaps)
    Activator.CreateInstance(classMap);

I have created a github repository for this example – https://github.com/alexjamesbrown/mongo-csharp-classmap/tree/proof-of-concept-example
(for those not familiar with git, you can download a zip here)
I’ve used a separate branch, as I want to formalise this into a reusable nuget package, subject to community approval!

2 responses to “Managing multiple MongoDB C# class maps – Part 1”

  1. Joseph Ferris avatar
    Joseph Ferris

    Know that it is an older article, but thank you for this. The MongoDB documentation, while thorough, is all over the place. I have been trying to find a clean way to add mapping for domain objects without bleeding any concerns into my architecture. This is much more elegant than anything I was trying to do.

    1. Alex avatar
      Alex

      No problem;
      There was talk of this being incorporated into the core driver, but not sure what happened to that idea!

Leave a Reply

Your email address will not be published. Required fields are marked *