MongoDB doesn’t serialise C# read-only properties

C# 6 introduced getter-only auto-properties, which are a great way of conveying immutability for your classes. However, by default MongoDB ignores these properties from its class maps, as it can’t deserialise them back into a class.

To automatically map read-only properties, you can use a custom convention:

/// <summary>
/// A convention to ensure that read-only properties are automatically mapped (and therefore serialised).
/// </summary>
public class MapReadOnlyPropertiesConvention : ConventionBase, IClassMapConvention
    private readonly BindingFlags _bindingFlags;

    public MapReadOnlyPropertiesConvention() : this(BindingFlags.Instance | BindingFlags.Public) {}

    public MapReadOnlyPropertiesConvention(BindingFlags bindingFlags)
        _bindingFlags = bindingFlags | BindingFlags.DeclaredOnly;

    public void Apply(BsonClassMap classMap)
        var readOnlyProperties = classMap
            .Where(p => IsReadOnlyProperty(classMap, p))

        foreach (var property in readOnlyProperties)

    private static bool IsReadOnlyProperty(BsonClassMap classMap, PropertyInfo propertyInfo)
        if (!propertyInfo.CanRead) return false;
        if (propertyInfo.CanWrite) return false; // already handled by default convention
        if (propertyInfo.GetIndexParameters().Length != 0) return false; // skip indexers

        var getMethodInfo = propertyInfo.GetMethod;

        // skip overridden properties (they are already included by the base class)
        if (getMethodInfo.IsVirtual && getMethodInfo.GetBaseDefinition().DeclaringType != classMap.ClassType) return false;

        return true;

And then register the convention on startup:

var conventionPack = new ConventionPack
    new MapReadOnlyPropertiesConvention()

ConventionRegistry.Register("Conventions", conventionPack, _ => true);

This will ensure that read-only properties are serialised, which is all we needed. However, to then deserialise properties back into a class, you may need a constructor. You can find an extended example of this at (which this code is based on) – we had constructor chains that this didn’t seem to work with, but it might help guide you further.