You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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}")
        };
    }
}

核心修复说明

  1. 规范读取流程:严格按照BsonReader的状态机读取文档,找到目标字段后跳过剩余元素,避免读取位置混乱。
  2. try-finally保证状态恢复:无论是否发生异常,都强制回到初始书签位置,确保后续反序列化流程不受影响。
  3. 简化类型匹配逻辑:使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 17:37:33