Created a dotnetfiddle here: https://dotnetfiddle.net/dUbJss
Given data that needs to be deserialized that is already written to hundreds of mongo databases:
[
{
"_id": ObjectId(aa),
"shape": {
"_bsonName": "Company::Mapping::Geofences::GeoRectangle",
"geoJson": ...,
"bottom": "1", "left": 2, "right": 3, "top": 4
}
},
{
"_id": ObjectId(ab),
"shape": {
"_bsonName": "Company::Mapping::Geofences::GeoPolygon",
"points": [[-79,36],[-80,37],[-79,38]]
}
]
[DiscriminatorValue]
can be one of:
public class Geofence
{
public ObjectId Id { get;set; }
[BsonElement("shape")]
public GeofenceShape Shape { get; set; }
}
public class GeofenceShape
{
[BsonElement("_bsonName")]
public string BsonName { get; set; }
}
public class RectangleGeoShape : GeofenceShape
{
public decimal Bottom { get; set; }
public decimal Left { get; set; }
public decimal Right { get; set; }
public decimal Top { get; set; }
}
public class PolygonGeoShape : GeofenceShape
{
public decimal[][] Points { get; set; }
}
Basically the _t field that mongo normally uses for discriminator needs to be "_bsonName" but I also have to read that _bsonName field and return our C# types: RectangleShape|EllipseShape|PolygonShape
So I created an IDiscriminatorConvention class
public class GeofenceShapeDiscriminatorConvention : IDiscriminatorConvention
{
public string ElementName => "_bsonName";
public Type GetActualType(IBsonReader bsonReader, Type nominalType)
{
var bookmark = bsonReader.GetBookmark();
string bsonName = null;
bsonReader.ReadStartDocument();
if(bsonReader.FindElement("_bsonName"))
{
bsonName = bsonReader.ReadString();
}
bsonReader.ReturnToBookmark(bookmark);
switch (bsonName)
{
case "Company::Mapping::Geofences::GeoEllipse":
return typeof(EllipseGeofenceShape);
case "Company::Mapping::Geofences::GeoRectangle":
return typeof(RectangleGeofenceShape);
case "Company::Mapping::Geofences::GeoPolygon":
return typeof(PolygonGeofenceShape);
default:
throw new NotSupportedException($"Unexpected shape._bsonName of {bsonName}.");
}
}
public BsonValue GetDiscriminator(Type nominalType, Type actualType)
{
switch(actualType)
{
case Type _ when actualType == typeof(EllipseGeofenceShape):
return "Company::Mapping::Geofences::GeoEllipse";
case Type _ when actualType == typeof(RectangleGeofenceShape):
return "Company::Mapping::Geofences::GeoRectangle";
case Type _ when actualType == typeof(PolygonGeofenceShape):
return "Company::Mapping::Geofences::GeoPolygon";
default:
throw new ApplicationException($"Unexpected type '{actualType.FullName}' when serializing.");
}
}
}
unfortunately, this code fails with a StackOverflowException, where the Mongo code keeps calling GetActualType over and over and over again.
Looked through their source code, and theres not a lot of proper examples.
Of note, if I skip the bsonReader.ReturnToBookmark(bookmark);
then I get a different error, basically failed to GetBsonType() on a node of type value
(i forget the exact wording)
Instead of writing a discriminator convention from scratch, you could derive one from StandardDiscriminatorConvention
and use the BsonDiscriminator
attributes, e.g.:
internal class GeofenceDiscriminatorConvention : StandardDiscriminatorConvention
{
public GeofenceDiscriminatorConvention() : base("_bsonName")
{
}
public static void Register()
{
BsonSerializer.RegisterDiscriminatorConvention(typeof(GeofenceShape), new GeofenceDiscriminatorConvention());
}
public override BsonValue GetDiscriminator(Type nominalType, Type actualType)
{
switch (actualType)
{
case Type _ when actualType == typeof(EllipseGeofenceShape):
return "Company::Mapping::Geofences::GeoEllipse";
case Type _ when actualType == typeof(RectangleGeofenceShape):
return "Company::Mapping::Geofences::GeoRectangle";
case Type _ when actualType == typeof(PolygonGeofenceShape):
return "Company::Mapping::Geofences::GeoPolygon";
default:
throw new ApplicationException($"Unexpected type '{actualType.FullName}' when serializing.");
}
}
}
On the GeofenceShape
classes, you set the corresponding discriminator values like this:
// Base class
[BsonKnownTypes(typeof(EllipseGeofenceShape), typeof(PolygonGeofenceShape), typeof(RectangleGeofenceShape))]
internal class GeofenceShape
{
[BsonRepresentation(MongoDB.Bson.BsonType.ObjectId)]
public string Id { get; set; }
}
// Derived class
[BsonDiscriminator("Company::Mapping::Geofences::GeoPolygon")]
internal class PolygonGeofenceShape : GeofenceShape
{
}