Storing polymorphic classes in MongoDb using C#

NoSql databases like Mongo give us advantage of storing our classes directly in data store without worrying too much about schemas.Thus saving us from object-relational impedance mismatch.Common scenario that arises from storing classes is how to handle inheritance hierarchies.

In this post I will discuss on how mongo db handles polymorphic classes or inheritance.I am using official c# driver for MongoDb.

To start with first thing to know is that MongoDb fully supports polymorphic classes and all the classes in your class hierarchies can be part of the same mongo collection.How it does that is with a  concept called Type Discriminators.

To fully understand the concept discussed you need to have basic knowledge of how MongoDb works.You can get started with MonogDb official documentation.Also there is an excellent pluralsight course on Using MongoDB with ASP.NET MVC which can get you started on using MongoDb with ASP.NET MVC and C#.

Type Discriminator

The way mongo distinguishes between various hierarchical types is by including a field with name ‘_t’  called type discriminator as shown below.

image

Lets consider a simple class hierarchy to illustrate the concept.

image

Point here is that while saving the data I am always going to use base class as shown below and type discriminators will help in serializing and de-serializing the actual type.

 var writeConcernResult = _dataContext.ContentCollection.Save<ContentBase>(content);

There are multiple ways in which we can distinguish the type and hence mongo provides two inbuilt conventions called Type discriminator conventions.

  • ScalarDiscriminatorConvention : In this case ‘_t’ parameter contains by default the type name as shown above in the screenshot.
  • HierarchialDiscriminatorConvention : This convention comes into play when you specify one of the class in the hierarchy as something called root class as shown below
   [BsonDiscriminator(RootClass = true)]
   [BsonKnownTypes(typeof(TextContent),typeof(FileContent))]
    public abstract  class ContentBase
    {

Additionally you need to specify the known types using BsonKnownTypes  attribute so that while de-serializing the content to objects correct type is created.

Below is how type discriminator is stored in this case.

image

Notice how the whole hierarchy is displayed as array.

Custom Type Discriminator Convention

You also have option of writing your custom type discriminator convention.Using custom convention you can change the way type discriminator is stored or what is stored.

For our example we will just change the name of element which stores type discriminator i.e. currently we have ‘_t’ and we will change it to say _contentType’.This may actually be required when you are working with different mongo drivers.

public class ContentTypeDiscriminatorConvention : IDiscriminatorConvention
    {
        public string ElementName
        {
            get { return "_contentType"; }
        }

        public Type GetActualType(MongoDB.Bson.IO.BsonReader bsonReader, Type nominalType)
        {
            var bookmark = bsonReader.GetBookmark();
            bsonReader.ReadStartDocument();
            string typeValue = string.Empty;
            if (bsonReader.FindElement(ElementName))
                typeValue = bsonReader.ReadString();
            else
                throw new NotSupportedException();

            bsonReader.ReturnToBookmark(bookmark);
            return Type.GetType(typeValue);
        }

        public MongoDB.Bson.BsonValue GetDiscriminator(Type nominalType, Type actualType)
        {
            return actualType.Name;
        }
    }

Below are the results.

image

Recommended Books on Amazon:

MongoDB: The Definitive Guide

MongoDB Applied Design Patterns

50 Tips and Tricks for MongoDB Developers

Tagged on: ,

4 thoughts on “Storing polymorphic classes in MongoDb using C#

  1. Stanko

    Great post!

    Thanks for the explanation. I’ve made an example that works for me and I want to share with everybody:

    // Model
    [BsonDiscriminator(RootClass = true)]
    [BsonKnownTypes(typeof(Student), typeof(Pupil))]
    public class User
    {
    [BsonId]
    public int Id { get; set; }

    [BsonElement]
    [BsonRepresentation(BsonType.String)]
    public string Name { get; set; }

    public override string ToString()
    {
    return $"Id: {Id}, Name: {Name}";
    }
    }

    public class Student : User
    {
    public string Departmant { get; set; }

    public override string ToString()
    {
    return $"{base.ToString()}, Department: {Departmant}";
    }
    }

    public class Pupil : User
    {
    public string SchoolName { get; set; }

    public override string ToString()
    {
    return $"{base.ToString()}, SchoolName: {SchoolName}";
    }
    }


    // Usage
    [Test]
    public void ReadBase()
    {
    var database = new LocalMongoSource().GetDatabase();
    var collection = database.GetCollection("User");

    foreach (var user in collection.AsQueryable())
    {
    Console.WriteLine(user);
    }
    }

    [Test]
    public void WriteStudent()
    {
    var database = new LocalMongoSource().GetDatabase();
    var collection = database.GetCollection("User");

    collection.InsertOne(new Student()
    {
    Id = 30,
    Departmant = "E1",
    Name = "SampleName",
    });

    collection.InsertOne(new Pupil()
    {
    Id = 50,
    SchoolName = "SchoolTest",
    Name = "NewName",
    });
    }

  2. LukeW

    Very clear and approachable article introducing the topic! Would love to see a follow-up that discussing querying a polymorphic document collection. My experience thus far is things work fine as long as you know the type of document for which you are querying, but I’m still looking for a good example of dynamically discovering and deserializing a document when the type is unknown. For example, I want to deserialize the documents to their respective types and then use C# type-checking to implement behavior based on the object type. Do you have any examples of something like this?

Leave a Reply to Jagmeet Cancel reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>