Polymorphic Objects
On this page
Overview
Polymorphic objects inherit properties and methods from one or more parent classes. These objects require special mapping to ensure that the .NET/C# Driver correctly serializes them to and from BSON documents.
This guide explains the following:
How to deserialize polymorphic types
The discriminator conventions included with the .NET/C# Driver
How to create custom discriminator conventions
The examples on this page use the following inheritance hierarchy:
public class Animal { } public class Cat : Animal { } public class Dog : Animal { } public class Lion : Cat { } public class Tiger : Cat { }
Deserialize Polymorphic Objects
Before the serializer can deserialize any polymorphic objects, you must document the relationship of all classes in the inheritance hierarchy.
If you're using the automapper to map your classes, apply the [BsonKnownTypes]
attribute to each base class in the hierarchy. Pass each class that directly inherits
from the base class as an argument.
The following example shows how to apply the [BsonKnownTypes]
attribute to
classes in the example Animal
hierarchy:
[ ]public class Animal { } [ ]public class Cat : Animal { } public class Dog : Animal { } public class Lion : Cat { } public class Tiger : Cat { }
Note
Using BsonKnownTypes
Apply the [BsonKnownTypes]
attribute only to parent classes. Pass as arguments
only the types that directly inherit from the class, not all child classes in
the hierarchy.
If you're creating a class map manually, call the
BsonClassMap.RegisterClassMap<T>()
method for every class in the hierarchy, as shown
in the following example:
BsonClassMap.RegisterClassMap<Animal>(); BsonClassMap.RegisterClassMap<Cat>(); BsonClassMap.RegisterClassMap<Dog>(); BsonClassMap.RegisterClassMap<Lion>(); BsonClassMap.RegisterClassMap<Tiger>();
Use Discriminators
In MongoDB, a discriminator is a field added to a document to identify the class
to which the document deserializes. When a collection contains more than one type from a
single inheritance hierarchy, discriminators ensure that each
document deserializes to the right class. The .NET/C# Driver stores the discriminator
value in a field named _t
in the BSON document. Generally, _t
is the second
field in the BSON document after _id
.
Discriminator conventions define the value stored in the discriminator field. In this section, you can learn about the discriminator conventions included with the .NET/C# Driver and how to create custom discriminator conventions.
ScalarDiscriminatorConvention
By default, the .NET/C# Driver uses the ScalarDiscriminatorConvention
. According
to this convention, the .NET/C# Driver sets the value of the _t
field to the name of
the class from which the document was serialized.
Suppose you create an instance of the example Animal
class and each of its
subclasses. If you serialize these objects to a single collection, the
.NET/C# Driver applies the ScalarDiscriminatorConvention
and the corresponding
BSON documents appear as follows:
{ _id: ..., _t: "Animal", ... } { _id: ..., _t: "Cat", ... } { _id: ..., _t: "Dog", ... } { _id: ..., _t: "Lion", ... } { _id: ..., _t: "Tiger", ... }
The ScalarDiscriminatorConvention
uses concise discriminator values, but can be
difficult to run a query on. For example, to find all documents of type or subtype Cat
,
you must explicitly list each class you're looking for:
var query = coll.AsQueryable().Where( item => item.GetType() == typeof(Cat) || item.GetType() == typeof(Lion) || item.GetType() == typeof(Tiger));
Note
OfType<T>() and the is Operator
When checking the type of a scalar discriminator, use the Where
syntax shown in
the preceding code example. If you try to use the Aggregate().OfType<T>()
method,
or if you pass an expression containing the is
operator to the
Aggregate().Match()
method, the driver throws an exception.
HierarchicalDiscriminatorConvention
To simplify queries against your collection of polymorphic types, you can use the
HierarchicalDiscriminatorConvention
. According to this convention, the value of _t
is an array of all classes in the inheritance hierarchy of the document's type.
To use the HierarchicalDiscriminatorConvention
, label the base class of your
inheritance hierarchy as the root class. If you're using the automapper,
label the root class by applying the
[BsonDiscriminatorAttribute]
attribute to the class and passing RootClass = true
as an argument. The following code example labels the Animal
class as the
root of the example inheritance hierarchy:
[ ][ ]public class Animal { }
If you're creating a class map manually, call the SetIsRootClass()
method and pass
true
as an argument when you register the class map for the root class. The following
code example registers class maps for all five example classes but labels only the
Animal
class as the root of the inheritance hierarchy:
BsonClassMap.RegisterClassMap<Animal>(classMap => { classMap.AutoMap(); classMap.SetIsRootClass(true); }); BsonClassMap.RegisterClassMap<Cat>(); BsonClassMap.RegisterClassMap<Dog>(); BsonClassMap.RegisterClassMap<Lion>(); BsonClassMap.RegisterClassMap<Tiger>();
Suppose you label the example Animal
class as the root of the inheritance hierarchy,
and then create an instance of the Animal
class and each of its
subclasses. If you serialize these objects to a single collection, the .NET/C# Driver
applies the HierarchicalDiscriminatorConvention
and the corresponding
BSON documents appear as follows:
{ _id: ..., _t: "Animal", ... } { _id: ..., _t: ["Animal", "Cat"], ... } { _id: ..., _t: ["Animal", "Dog"], ... } { _id: ..., _t: ["Animal", "Cat", "Lion"], ... } { _id: ..., _t: ["Animal", "Cat", "Tiger"], ... }
Important
Root Class Discriminator
Any document mapped to the root class still uses a string value for the discriminator field.
When using the HierarchicalDiscriminatorConvention
, you can search for all
documents of type or subtype Cat
by using a single boolean condition, as shown in
the following example:
var query = coll.Aggregate().Match(a => a is Cat);
Custom Discriminator Conventions
If you're working with data that doesn't follow the conventions used by the .NET/C# Driver--for example, data inserted into MongoDB by another driver or object mapper--you might need to use a different value for your discriminator field to ensure your classes align with those conventions.
If you're using the automapper, you can specify a custom value for a class's discriminator
field by applying the [BsonDiscriminator]
attribute to the class and passing
the custom discriminator value as a string argument. The following code example
sets the value of the discriminator field for the Animal
class to "myAnimalClass":
[ ]public class Animal { }
If you're creating a class map manually, call the SetDiscriminator()
method and pass
the custom discriminator value as an argument when
you register the class map. The following code example sets the value of the
discriminator field for the Animal
class to "myAnimalClass":
BsonClassMap.RegisterClassMap<Animal>(classMap => { classMap.AutoMap(); classMap.SetDiscriminator("myAnimalClass"); });
An instance of the previous Animal
class appears as follows after serialization:
{ "_id": "...", "_t": "myAnimalClass"}