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
.ClassType
.GetTypeInfo()
.GetProperties(_bindingFlags)
.Where(p => IsReadOnlyProperty(classMap, p))
.ToList();
foreach (var property in readOnlyProperties)
{
classMap.MapMember(property);
}
}
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 https://stackoverflow.com/questions/39604820/serialize-get-only-properties-on-mongodb (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.
Björn’s suggestion is useful for only assigned properties by constructor. That method is useless for other properties. Your class is working as expected. Viva reflection 🙂 I was gonna try the similar somethings but i found yours. Thanks for your help
Thanks for this, although it seems this has been built in.
This code works for me:
BsonClassMap.RegisterClassMap<Document>(cm => { cm.AutoMap(); new ImmutableTypeClassMapConvention().Apply(cm); });
Thanks for the tip Björn, perhaps I missed that when searching, or it was only available in a later version of MongoDB!