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
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!
Leave a Reply