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:
- "Company::Mapping::Geofences::GeoEllipse" -> c# type "EllipseGeoShape"
- "Company::Mapping::Geofences::GeoRectangle" -> c# type "RectangleGeoShape"
- "Company::Mapping::Geofences::GeoPolygon" -> c# type "PolygonGeoShape"
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
StandardDiscriminatorConventionand use theBsonDiscriminatorattributes, e.g.:On the
GeofenceShapeclasses, you set the corresponding discriminator values like this: