如何解决MongoDB反序列化‘类型无合适构造函数或Add方法’错误?
我花了一整天翻遍Stack Overflow和各类技术博客找解决方案,搜出来的同类问题都和我的情况对不上,只能自己摸索。现在把解决思路和发现分享出来,帮到别人或者以后忘了的自己——毕竟我总记不住自己写的解决方案,指不定哪天又搜到这篇 :)
问题现象
抛出如下异常:
System.FormatException
HResult=0x80131537
消息:反序列化Domain.SeedWork.Aggregate1[[System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]类的EventsToPublish字段时发生错误:类型'DomainManagedList1[[Domain.Events.EventToPublish, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'没有合适的构造函数或Add方法。
来源:MongoDB.Bson
调用堆栈:
在 MongoDB.Bson.Serialization.BsonClassMapSerializer1.DeserializeMemberValue(BsonDeserializationContext context, BsonMemberMap memberMap) 在 MongoDB.Bson.Serialization.BsonClassMapSerializer1.DeserializeClass(BsonDeserializationContext context)
在 MongoDB.Bson.Serialization.BsonClassMapSerializer1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) 在 MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer1 serializer, BsonDeserializationContext context)
在 MongoDB.Driver.Core.Operations.CursorBatchDeserializationHelper.DeserializeBatch[TDocument](RawBsonArray batch, IBsonSerializer1 documentSerializer, MessageEncoderSettings messageEncoderSettings) 在 MongoDB.Driver.Core.Operations.FindOperation1.CreateFirstCursorBatch(BsonDocument cursorDocument)
在 MongoDB.Driver.Core.Operations.FindOperation1.CreateCursor(IChannelSourceHandle channelSource, IChannelHandle channel, BsonDocument commandResult) 在 MongoDB.Driver.Core.Operations.FindOperation1.d__129.MoveNext()
在 MongoDB.Driver.Core.Operations.FindOperation1.<ExecuteAsync>d__128.MoveNext() 在 MongoDB.Driver.OperationExecutor.<ExecuteReadOperationAsync>d__31.MoveNext()
在 MongoDB.Driver.MongoCollectionImpl1.<ExecuteReadOperationAsync>d__991.MoveNext()
在 MongoDB.Driver.MongoCollectionImpl1.<UsingImplicitSessionAsync>d__1071.MoveNext()
在 Infrastructure.MongoDb.Repositories.MongoRepository2.<FindAsync>d__8.MoveNext() 位置 C:\dev\domain-driven-customer-service\src\Infrastructure\MongoDb\Repositories\MongoRepository.cs:行号 65 在 Infrastructure.MongoDb.Repositories.Repository2.d__3.MoveNext() 位置 C:\dev\domain-driven-customer-service\src\Infrastructure\MongoDb\Repositories\Repository.cs:行号 25
在 Api.Program.d__0.MoveNext() 位置 C:\dev\domain-driven-customer-service\src\Api\Program.cs:行号 36此异常最初在此调用堆栈中抛出:
[外部代码]内部异常1: BsonSerializationException: 类型'Domain.Aggregates.DomainManagedList`1[[Domain.Events.EventToPublish, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'没有合适的构造函数或Add方法。
问题原因
MongoDB的Bson序列化器默认处理集合类型时,会尝试通过无参构造函数创建实例,再调用Add(T item)方法逐个添加元素完成反序列化。而自定义的DomainManagedList<T>类没有满足这两个条件中的任意一个,导致序列化器无法完成反序列化操作。
解决方案
方案一:给自定义集合类添加必要的构造函数和Add方法
直接修改DomainManagedList<T>类,补充无参构造函数和Add方法,让Bson序列化器可以正常识别:
public class DomainManagedList<T> : IEnumerable<T> { private readonly List<T> _internalList = new List<T>(); // 添加无参构造函数 public DomainManagedList() { } // 添加支持单个元素的Add方法 public void Add(T item) { _internalList.Add(item); // 这里可以添加自定义的业务逻辑,比如校验、事件触发等 } // 实现IEnumerable<T>的必要接口方法 public IEnumerator<T> GetEnumerator() { return _internalList.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }
方案二:注册自定义Bson序列化器
如果不想修改DomainManagedList<T>的代码结构,可以自定义一个序列化器,告诉MongoDB如何处理这个类型:
// 自定义序列化器,继承EnumerableSerializerBase基类 public class DomainManagedListSerializer<T> : EnumerableSerializerBase<DomainManagedList<T>, T> { // 创建自定义集合实例 protected override DomainManagedList<T> CreateInstance() { return new DomainManagedList<T>(); } // 定义如何向集合中添加元素 protected override void AddItem(DomainManagedList<T> instance, T item) { // 调用自定义集合的添加逻辑,如果有内部方法也可以直接用 instance.Add(item); } } // 在MongoDB初始化时注册这个泛型序列化器 BsonSerializer.RegisterGenericSerializerDefinition( typeof(DomainManagedList<>), typeof(DomainManagedListSerializer<>) );
方案三:将自定义集合映射为普通List存储
通过Bson类映射,把DomainManagedList<T>类型的字段序列化为普通List<T>存储到MongoDB,反序列化时再转换回自定义类型:
// 注册Aggregate类的Bson映射规则 BsonClassMap.RegisterClassMap<Aggregate<string>>(cm => { cm.AutoMap(); // 把EventsToPublish字段序列化为List<EventToPublish> cm.MapMember(a => a.EventsToPublish) .SetSerializer(new EnumerableSerializer<List<EventToPublish>, EventToPublish>()) .SetElementName("EventsToPublish"); // 反序列化完成后,将List转换为自定义的DomainManagedList cm.AfterDeserialization(obj => { obj.EventsToPublish = new DomainManagedList<EventToPublish>(obj.EventsToPublish.ToList()); }); });
内容的提问来源于stack exchange,提问作者sbp

