Why does BsonClassMap.KnownTypes have no setter?

800 views
Skip to first unread message

Bryan Migliorisi

unread,
Jan 6, 2012, 1:10:19 PM1/6/12
to mongodb...@googlegroups.com
I would like to use reflection to inform the driver of types that inherit from a base class.  Finding the classes is a no brainer, and this approach would be much easier to maintain than always needing to remember to inform the base class of its known types using BsonKnownTypes attribute.

I thought I could do it using the BsonClassMap.RegisterClassMap method but the BsonClassMap parameter that gets passed to the callback method exposes a KnownTypes read-only property.

Why can't I tell the BsonClassMap of the known types here?  Is there another way to do this?

Specifically, I have an array of Type's that I would like to register as known types for a given Type:

List<Type> myTypes = { ... };
BsonClassMap.RegisterClassMap<MyBaseClass>(cm => cm.KnownTypes = myTypes);
 -- OR --
BsonClassMap.RegisterClassMap<MyBaseClass>(cm => cm.RegisterKnownTypes(myTypes));


Am I missing something, or is there a technical reason why this cannot be done?

Thanks,
Bryan



Robert Stam

unread,
Jan 6, 2012, 1:16:49 PM1/6/12
to mongodb...@googlegroups.com
The idea was that if you were calling RegisterClassMap for the base class there was no need to set the KnownTypes because you would just call RegisterClassMap for each of the derived classes.

KnownTypes is used by Automap to automatically Automap derived classes when the base class is Automapped. If you are not using Automap you don't need to set KnownTypes.

Bryan Migliorisi

unread,
Jan 6, 2012, 2:39:22 PM1/6/12
to mongodb...@googlegroups.com
So, just to make sure I understand what youre saying..

public class BaseClass {
      public BsonObjectId Id {get;set;}
      public string Prop1 {get;set;}
}

public class DerivedClass : BaseClass {
      public string Prop2 {get;set;} 
}

If I store an instance of DerivedClass into MongoDB, and pull it back out, the driver should recognize the descriminator and cast the object to an instance of DerivedClass - and this should all happen WITHOUT needing to inform BaseClass of DerivedClass using BsonKnownTypes attribute?

That was my original understanding - but I started seeing deserialization errors on a base class that has around 20 - 25 derived classes.  I cannot recall the exact message, but I was able to fix it by adding BsonKnownTypes attribute to the base class listing all of the derived classes.  If that is inconsistent with your intentions, I'll try to reproduce it and\or figure out what I am doing to cause it.


Robert Stam

unread,
Jan 6, 2012, 2:46:21 PM1/6/12
to mongodb...@googlegroups.com
The driver needs class maps for both the BaseClass and the DerivedClass in order to successfully deserialize DerivedClass. There are two ways those class maps can come into existence:

1. Annotate BaseClass with KnownTypes and let Automap handle both of them
2. You call RegisterClassMap for BOTH BaseClass and DerivedClass

The point I was making about KnownTypes is that it only comes into play if you allow BaseClass to be Automapped. If you choose to call RegisterClassMap yourself for the BaseClass then you also need to call RegisterClassMap for any DerivedClasses.

Bryan Migliorisi

unread,
Jan 6, 2012, 2:58:44 PM1/6/12
to mongodb...@googlegroups.com
Right, so that doesnt solve the problem of needing to manually inform the driver of each individual derived class - either through the attribute or through the RegisterClassMap method.

I have a base class that has around 20-225 derived classes that need to be stored and retrieved and the list is growing.  

In a perfect world, I want to be able to create additional derived classes without needing to worry about adding it as a known type via attribute or via RegisterClassMap.

Ideally there would be a way for me to pass a List<Type> list (which could be figured out with 1 line of reflection) to the driver containing all of the known Type's that are derived from the base class:
BsonClassMap.RegisterClassMap<MyBaseClass>(cm => cm.RegisterKnownTypes(myTypes)); 

This way, there is one line of code in my bootstrapper that finds all of the types and registers them, removing the need for attributes or individual class mapping.  Ultimately, this just makes code maintenance alot easier and less error prone.

I hope I am making sense!

Robert Stam

unread,
Jan 6, 2012, 3:22:47 PM1/6/12
to mongodb...@googlegroups.com
Are you sure there is a 1 line of reflection code that gives you all the derived types of a type? I'd like to learn how to do that!

OK, I looked at the C# driver code a little more carefully and KnownTypes is actually used when a class map is Frozen, which is a little bit later in time than when it is Automapped, so what you are suggesting is technically feasible.

You can also do it already in just a slightly different way:

    Type[] myDerivedTypes; // comes from somewhere
    foreach (var type in myDerivedType)
    {
        LookupClassMap(type); // will result in type being Automapped if the class map doesn't already exist

Bryan Migliorisi

unread,
Jan 6, 2012, 3:53:29 PM1/6/12
to mongodb...@googlegroups.com
Ah! Thanks Robert, I will test that approach you mentioned and see what happens.  Maybe this solves my problem after all.

This is my boot strapper (using WebActivator).  It gets current assembly (by way of figuring out which assembly this current method is from), finds all types, and filters out any types which are not derived from BaseClass.  (This may need fine tuning based project & assembly structure):

[assembly: WebActivator.PreApplicationStartMethod(typeof(MyApp.Bootstrap.RegisterBsonClassMap), "Start")]
public static class RegisterBsonClassMap
    {
        public static void Start()
        {

            var knownTypes = Assembly.GetAssembly(typeof (RegisterBsonClassMap)).GetTypes().Where(type => type.IsSubclassOf(typeof (BaseClass))).ToList();
            knownTypes.Select(BsonClassMap.LookupClassMap);
        }
    }

Robert Stam

unread,
Jan 6, 2012, 4:45:47 PM1/6/12
to mongodb...@googlegroups.com
Ah... a LONG line of reflection.

I didn't think there was any way to directly get the derived classes, but this approach works (as long as the derived types are in the same assembly).

Bryan Migliorisi

unread,
Jan 6, 2012, 11:08:58 PM1/6/12
to mongodb...@googlegroups.com
I cheated - ReSharper actually breaks that line into a couple of line automatically, making it easier to read :)

Ok so, I have verified that the approach you suggested works. Thank you for that :)

I also discovered why I randomly encountered exceptions in the past.

As I mentioned, I have 20 - 25 classes that inherit from my base class.  This is actually akin to a queue with several different classes that are queue items.  I push them into the queue as MongoDB doc's and then pop them off the queue and send them to the correct processor based on the the class type.

I observed that the queue processor was throwing exceptions randomly.  After experimenting tonight based on this discussion and browsing through the source, I realized that when a class is converted to a document, a BsonClassMap is created for that class, if it does not already exist.

Originally, I was not informing my app in any way (such as attributes or method calls) about my derived classes - but it was working, sometimes.  As it turns out, it worked sometimes because I was writing documents before reading any from the queue, which was creating the class maps automatically on the fly.  If I read a document of the same nominal type out of MongoDB, it worked flawlessly because the ClassMap had been created when I wrote a document of that type to the DB.

There were in some cases, documents in the database that may have been there for some time (days in some cases) and the application didnt have a class map for that type (because the process was restarted, for example) and so when reading the document, it could not properly determine the type.

Just figured I would explain what my problem was and how I solved it.  Might be worth being archived somewhere on the web in case someone else runs into this issue.  I hope it made sense.  

Thanks Robert - great work on the driver.  Looking forward to some LINQ goodness!

Joel Lieberman

unread,
Nov 20, 2012, 11:44:57 AM11/20/12
to mongodb...@googlegroups.com
Thanks to you as well Bryan. I was looking for a way to specify BsonKnownTypes dynamically at runtime, and this thread has provided the answer. I even encountered the same problem where MongoDB would not recognize derived types unless they had previously been written to a collection. Now I understand it was LookupClassMap auto-mapping behind the scenes.

Excellent info.
Reply all
Reply to author
Forward
0 new messages