Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fetch inherited instances from MongoDB using C#

I'm using the official MongoDb C# driver.

My scenario: I store objects into MongoDb. All objects are instances of classes that inherit from the same root class. At design time I do not know all classes that can be stored (i.e they can be plugged in) - so I need some way to tell the serializer/driver how to map the classes to documents (descriminators in the document).

Anyone got any ideas?

like image 278
Jan Ohlson Avatar asked Jun 07 '11 12:06

Jan Ohlson


2 Answers

The official C# driver will write a "_t" discriminator value whenever the actual type of an object is different than the nominal type. So for example:

MyRootClass obj = new MyDerivedClass();
collection.Insert(obj);

The Insert statement could also have been written:

collection.Insert<MyRootClass>(obj);

but it's easier to let the compiler infer the type parameter.

Since the actual type of obj is different than the nominal type the "_t" discriminator will be written.

When reading back the object you will have to ensure that MyDerivedClass has been properly registered:

BsonClassMap.RegisterClassMap<MyDerivedClass>();

or the serializer won't recognize the discriminator (this may seem like a restriction, but it's only logical that the serializer can only work with types it knows about).

You mentioned that you don't know the classes at compile time, so the above registration code must be invoked dynamically. One way to do it is:

Type myDerivedClass; // your plugged-in class
var registerClassMapDefinition = typeof(BsonClassMap).GetMethod("RegisterClassMap", new Type[0]);
var registerClassMapInfo = registerClassMapDefinition.MakeGenericMethod(myDerivedClass);
registerClassMapInfo.Invoke(null, new object[0]);

Technically, the serialization is not using reflection; it is metadata driven. Reflection is used once to construct the class map, but after that the class map is used directly without reflection, and the overhead is rather low.

like image 165
Robert Stam Avatar answered Oct 18 '22 08:10

Robert Stam


I wrote an helper class improving the excellent answer of Robert Stam and allowing the same parameters as the static BsonClassMap.RegisterClassMap<...>() method.

public class MyBsonClassMap
{
    public static void RegisterClassMap(Type type)
    {
        Type bsonClassMapType = typeof(BsonClassMap<>).MakeGenericType(new Type[] { type });
        BsonClassMap bsonClassMap = (BsonClassMap)Activator.CreateInstance(bsonClassMapType);
        BsonClassMap.RegisterClassMap(bsonClassMap);
    }

    public static void RegisterClassMap(Type type, Action<BsonClassMap> classMapInitializer)
    {
        Type bsonClassMapType = typeof(BsonClassMap<>).MakeGenericType(new Type[] { type });
        BsonClassMap bsonClassMap = (BsonClassMap)Activator.CreateInstance(bsonClassMapType);
        classMapInitializer(bsonClassMap);
        BsonClassMap.RegisterClassMap(bsonClassMap);
    }
}

Now I am able to register a type that was unknown at compile time with almost the same syntax as a known one:

Type unknownType; // is the type that was unknown at compile time
MyBsonClassMap.RegisterClassMap(unknownType);

or

MyBsonClassMap.RegisterClassMap(unknownType, cm =>
     cm.AutoMap());

These methods should be available in the C# driver.

like image 1
Tok' Avatar answered Oct 18 '22 08:10

Tok'