Mongo Bson序列化:非标准鉴别器字段的多态反序列化问题
MongoDB自定义鉴别器反序列化问题解决
问题背景
现有MongoDB中存储的地理围栏数据,需要映射到C#继承体系类中,要求使用_bsonName字段替代Mongo默认的_t作为类型鉴别器。数据结构示例:
[ { "_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]] } } ]
鉴别器与C#类型映射关系:
- "Company::Mapping::Geofences::GeoEllipse" →
EllipseGeoShape - "Company::Mapping::Geofences::GeoRectangle" →
RectangleGeoShape - "Company::Mapping::Geofences::GeoPolygon" →
PolygonGeoShape
对应的C#实体定义:
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; } }
自行实现IDiscriminatorConvention后,运行时出现StackOverflowException(驱动反复调用GetActualType);移除ReturnToBookmark则触发读取状态错误(如"failed to GetBsonType() on a node of type value")。
正确解决方案
问题根源在于原实现中读取文档的方式破坏了Mongo驱动的读取状态,导致驱动重复触发鉴别器逻辑。以下是修复后的鉴别器实现:
public class GeofenceShapeDiscriminatorConvention : IDiscriminatorConvention { public string ElementName => "_bsonName"; public Type GetActualType(IBsonReader bsonReader, Type nominalType) { var bookmark = bsonReader.GetBookmark(); string bsonName = null; try { // 检查当前节点是否为文档类型 if (bsonReader.ReadBsonType() == BsonType.Document) { bsonReader.ReadStartDocument(); // 遍历文档元素,仅提取_bsonName字段 while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { var elementName = bsonReader.ReadName(); if (elementName.Equals("_bsonName", StringComparison.Ordinal)) { bsonName = bsonReader.ReadString(); // 读取后跳过剩余值,避免干扰后续读取 bsonReader.SkipValue(); break; } else { // 跳过非目标字段 bsonReader.SkipValue(); } } bsonReader.ReadEndDocument(); } } finally { // 强制回到书签位置,保证驱动读取状态正常 bsonReader.ReturnToBookmark(bookmark); } // 根据鉴别器值返回对应类型 return bsonName switch { "Company::Mapping::Geofences::GeoEllipse" => typeof(EllipseGeoShape), "Company::Mapping::Geofences::GeoRectangle" => typeof(RectangleGeoShape), "Company::Mapping::Geofences::GeoPolygon" => typeof(PolygonGeoShape), _ => throw new NotSupportedException($"Unexpected shape._bsonName: {bsonName}") }; } public BsonValue GetDiscriminator(Type nominalType, Type actualType) { return actualType switch { Type t when t == typeof(EllipseGeoShape) => "Company::Mapping::Geofences::GeoEllipse", Type t when t == typeof(RectangleGeoShape) => "Company::Mapping::Geofences::GeoRectangle", Type t when t == typeof(PolygonGeoShape) => "Company::Mapping::Geofences::GeoPolygon", _ => throw new ApplicationException($"Unsupported type during serialization: {actualType.FullName}") }; } }
核心修复说明
- 规范读取流程:严格按照BsonReader的状态机读取文档,找到目标字段后跳过剩余元素,避免读取位置混乱。
- try-finally保证状态恢复:无论是否发生异常,都强制回到初始书签位置,确保后续反序列化流程不受影响。
- 简化类型匹配逻辑:使用C# 8.0+的模式匹配语法替代switch,代码更简洁易维护。
鉴别器注册
需要将自定义鉴别器注册到Mongo驱动中,有两种方式:
全局注册
BsonSerializer.RegisterDiscriminatorConvention(typeof(GeofenceShape), new GeofenceShapeDiscriminatorConvention());
类属性注册
直接在基类上标注特性:
[BsonDiscriminatorConvention(typeof(GeofenceShapeDiscriminatorConvention))] public class GeofenceShape { [BsonElement("_bsonName")] public string BsonName { get; set; } }
内容的提问来源于stack exchange,提问作者enorl76
相关产品推荐
相关产品推荐

