Since upgrading to .net 8 from .net 6 we are getting very unusual and sporadic behaviour from MongoDB (driver?). Every couple of days (sometimes weeks) the driver would throw the following exception during data read:
System.FormatException: An error occurred while deserializing the Id property of class X : Cannot deserialize a 'String' from BsonType 'ObjectId'.
Where X is a root class for 2 derived classes A & B.
public class X
{
// MongoDB ObjectId
public string Id { get; set; }
// Other common properties...
}
public class A : X
{
// Stuff... but without ID (it is inherited from X)
}
public class B : X
{
// Stuff... but without ID (it is inherited from X)
}
This exception would continue to be thrown until the app is restarted which makes it work again for a couple of days.
MongoDB.Driver version is 3.4.0, server version is 4.2.8.
- We are mapping our classes generically (no attributed whatsoever) before the connection is used.
- ClassMap is registered in a static constructor.
- Documents are only inserted in an X collection. A or B is queried using
OfType
on the X collection. - Inserted documents have the correct discriminator
_t: [ X, A (or B) ]
meaning it’s an array. I followed this web guide for polymorphism in MongoDB TDocument
can either be an X, A or B.- Our Mongo wrapper is a singleton.
BsonSerializer.TryRegisterSerializer(new GuidSerializer(GuidRepresentation.CSharpLegacy));
RegisterClassMap(typeof(TDocument), "Id");
// not ideal -- I know
var derivedTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetLoadableTypes())
.Where(type => type is not null && !type.IsAbstract && type.IsSubclassOf(typeof(TDocument)));
foreach (var derivedType in derivedTypes)
{
RegisterClassMap(derivedType, "Id");
}
ConventionRegistry.Register(
"DocumentStorage.IgnoreExtraElements",
new ConventionPack() { new IgnoreExtraElementsConvention(true) },
type => true
);
ConventionRegistry.Register(
"DocumentStorage.IgnoreNull",
new ConventionPack { new IgnoreIfNullConvention(true) },
type => true
);
}
private static void RegisterClassMap(Type classType, string idPropertyName)
{
if (classType.BaseType != typeof(object) && !BsonClassMap.IsClassMapRegistered(classType.BaseType))
RegisterClassMap(classType.BaseType, idPropertyName);
else if (classType is null || classType == typeof(object))
return;
if (!BsonClassMap.IsClassMapRegistered(classType))
{
var baseMap = BsonClassMap.IsClassMapRegistered(classType.BaseType)
? BsonClassMap.LookupClassMap(classType.BaseType)
: null;
var classMap = new BsonClassMap(classType, baseMap);
classMap.AutoMap();
classMap.SetIgnoreExtraElements(true);
if (classMap.BaseClassMap?.IdMemberMap is null)
{
classMap.SetIsRootClass(true);
classMap.MapIdProperty(idPropertyName)
.SetSerializer(new StringSerializer(BsonType.ObjectId))
.SetIdGenerator(StringObjectIdGenerator.Instance)
.SetIgnoreIfNull(false)
.SetIgnoreIfDefault(true);
}
classMap.Freeze();
BsonClassMap.RegisterClassMap(classMap);
}
}
Please let me know if you need more info.