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.

Something to add?

This site uses Akismet to reduce spam. Learn how your comment data is processed.