You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

.NET 8升级后MongoDB驱动间歇性抛出FormatException:无法将ObjectId反序列化为String

.NET 8升级后MongoDB驱动间歇性抛出FormatException:无法将ObjectId反序列化为String

这种间歇性的故障确实让人抓狂——明明刚重启时一切正常,运行几天就突然爆反序列化错误,重启又恢复。结合你描述的细节,我来帮你拆解可能的原因和对应的解决方向:

核心问题推测

你的问题本质是运行一段时间后,MongoDB驱动的类映射配置意外丢失/被覆盖,导致反序列化Id字段时,用了默认的StringSerializer(只能处理BSON字符串类型),而不是你注册的StringSerializer(BsonType.ObjectId)(能把BSON ObjectId转成C#字符串)。

为什么只在.NET 8出现?因为你用的MongoDB.Driver v3.4.0是2018年的老版本,完全没有适配.NET 8的 runtime 行为变化——比如静态构造函数执行时机、AppDomain类型加载逻辑、线程安全模型的细微调整,都可能触发旧驱动的隐藏bug。

具体解决步骤

1. 优先升级MongoDB驱动版本(最关键)

v3.4.0的驱动不仅不支持.NET 8,甚至对MongoDB Server 4.2的兼容性也只是基础级别。建议直接升级到MongoDB.Driver v27.x+(最新稳定版,完美支持.NET 8和MongoDB Server 4.2+)。

升级时注意:

  • 新驱动的类映射API基本兼容旧代码,但可以简化你的注册逻辑。
  • 升级后请测试多态查询(OfType<A>/OfType<B>)是否正常,确保 discriminator 逻辑依然工作。

2. 优化类映射注册逻辑(避免配置丢失)

你的通用类映射注册代码存在模糊点,容易在旧驱动+新.NET环境下出问题。建议替换为明确的类型注册:

static MongoWrapper()
{
    // 注册Guid序列化器
    BsonSerializer.TryRegisterSerializer(new GuidSerializer(GuidRepresentation.CSharpLegacy));

    // 先注册根类X的映射,明确配置Id字段
    BsonClassMap.RegisterClassMap<X>(cm =>
    {
        cm.AutoMap();
        cm.SetIsRootClass(true);
        cm.MapIdProperty(x => x.Id)
            .SetSerializer(new StringSerializer(BsonType.ObjectId))
            .SetIdGenerator(StringObjectIdGenerator.Instance)
            .SetIgnoreIfNull(false);
    });

    // 注册派生类A和B,继承根类的配置
    BsonClassMap.RegisterClassMap<A>(cm =>
    {
        cm.AutoMap();
        // 无需重复配置Id,自动继承X的映射
    });

    BsonClassMap.RegisterClassMap<B>(cm =>
    {
        cm.AutoMap();
    });

    // 注册全局约定
    ConventionRegistry.Register(
        "DocumentStorage.IgnoreExtraElements",
        new ConventionPack { new IgnoreExtraElementsConvention(true) },
        type => true
    );

    ConventionRegistry.Register(
        "DocumentStorage.IgnoreNull",
        new ConventionPack { new IgnoreIfNullConvention(true) },
        type => true
    );
}

如果还是需要通用注册派生类型,避免AppDomain.CurrentDomain.GetAssemblies()的不确定性(.NET 8中AppDomain的行为有变化),建议改用指定程序集扫描:

// 替换为你的业务程序集,避免扫描所有程序集带来的意外
var targetAssembly = typeof(X).Assembly;
var derivedTypes = targetAssembly.GetLoadableTypes()
    .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(X)));

foreach (var type in derivedTypes)
{
    BsonClassMap.LookupClassMap(type); // 自动触发AutoMap,继承根类配置
}

3. 排查线程安全与缓存问题

旧驱动v3.4.0在.NET 8的多线程环境下,可能存在类映射缓存的线程安全问题。可以临时添加调试代码,在报错前检查映射状态:

// 在查询前添加检查(仅调试用)
var xClassMap = BsonClassMap.LookupClassMap(typeof(X));
var idSerializer = xClassMap.IdMemberMap?.Serializer;
if (idSerializer is not StringSerializer stringSerializer || stringSerializer.Representation != BsonType.ObjectId)
{
    // 这里记录日志,确认映射是否被篡改,然后可以尝试重新注册
    // 注意:生产环境不要频繁重新注册,这只是临时调试手段
    BsonClassMap.UnregisterClassMap(typeof(X));
    // 重新注册X的类映射...
}

为什么重启能解决问题?

因为重启会重新执行静态构造函数,把正确的类映射配置加载到驱动的缓存中。但旧驱动在.NET 8环境下,这个缓存会在运行一段时间后(比如GC、线程池回收、程序集卸载等场景)意外失效,导致后续反序列化用了默认配置,触发FormatException。

内容来源于stack exchange

火山引擎 最新活动