如何解决MongoDB反序列化“Type没有合适的构造函数或Add方法”错误?

ggazkfy8  于 2022-12-26  发布在  Go
关注(0)|答案(1)|浏览(244)

为了尽快找到解决方案,我花了一整天的时间浏览了许多stackoverflow/internet博客,寻找这个问答标题中的问题。
已经有类似这个标题的问题贴出来了,但它们并不相同。很明显,我必须自己找到解决方案。把我的发现和方法贴在这里,这样它就可以帮助别人(或者我自己。我总是忘记我自己的解决方案,很有可能在遥远的将来我会再次出现在这个帖子上:)

    • 问题**:获取与下面类似的异常

HResult = 0x80131537消息=反序列化类Domain. SeedWork. Aggregate 1[[System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]: Type 'DomainManagedList的EventToPublish字段时出错1 Domain. Events. EventToPublish,Domain,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null"没有合适的构造函数或Add方法。源= MongoDB. Bson
堆栈跟踪:Bson.序列化. Bson类Map序列化器1.DeserializeMemberValue(BsonDeserializationContext context, BsonMemberMap memberMap) at MongoDB.Bson.Serialization.BsonClassMapSerializer 1.反序列化类(BsonDeserializationContext上下文)位于MongoDB. Bson.序列化. BsonClassMap序列化器1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer 1序列化器,BsonDeserializationContext上下文)位于MongoDB.驱动程序.核心.操作.游标批处理反序列化助手.反序列化批处理[TDocument](原始BsonArray批次,IBsonSerializer 1 documentSerializer, MessageEncoderSettings messageEncoderSettings) at MongoDB.Driver.Core.Operations.FindOperation 1.在MongoDB上创建第一个游标批处理(BsonDocument游标文档)。驱动程序。核心。操作。查找操作1.CreateCursor(IChannelSourceHandle channelSource, IChannelHandle channel, BsonDocument commandResult) at MongoDB.Driver.Core.Operations.FindOperation 1.d__129.MoveNext()在MongoDB.驱动程序.核心.操作.查找操作1.<ExecuteAsync>d__128.MoveNext() at MongoDB.Driver.OperationExecutor.<ExecuteReadOperationAsync>d__3 1.移动下一个()在MongoDB.驱动程序. MongoCollectionImpl 1.<ExecuteReadOperationAsync>d__99 1.移动下一个()在MongoDB.驱动程序. MongoCollectionImpl 1.<UsingImplicitSessionAsync>d__107 1.移动下一个()在基础设施. MongoDb.存储库. MongoRepository 2.<FindAsync>d__8.MoveNext() in C:\dev\domain-driven-customer-service\src\Infrastructure\MongoDb\Repositories\MongoRepository.cs:line 65 at Infrastructure.MongoDb.Repositories.Repository 2. d__3.移动下一个()在C:\dev\域驱动客户服务\src\基础设施\MongoDb\存储库\存储库. cs:第25行在Api.Program.d__0.移动下一个()在C:\dev\域驱动客户服务\src\Api\Program. cs:第36行
此异常最初在此调用堆栈中引发:【外部代码】
内部异常1:Bson序列化异常:类型"Domain. Aggregates. DomainManagedList" 1 Domain. Events. EventToPublish,Domain,Version = 1.0.0.0,区域性=非特定区域性,PublicKeyToken = null"没有合适的构造函数或Add方法。

tv6aics1

tv6aics11#

对我来说,这个问题发生是因为我用我的自定义列表类型DomainManagedList替换了List<T> EventsToPublish { get; set;}中的通用列表类型,DomainManagedList禁止了可以添加或修改列表中任何内容的访问方法(很像ImmutableList<T>)。原因是“为什么?”对于本文来说,这并不重要,但原因很简单,因为我们正在实现领域驱动的设计聚合模式,并且需要具有有限访问权限的领域实体因此只能从聚合本身(而不能从应用层)修改它们。
所以Type without fix的代码如下所示。

public class DomainManagedList<T> : IEnumerable<T>
{ // Custom type that broke deserialization 
    private readonly List<T> _itemList;

    public DomainManagedList()
    {
        _itemList = new List<T>();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ((IEnumerable<T>)_itemList).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    internal void Add(T item)
    {
        _itemList.Add(item);
    }

    internal void Remove(T item)
    {

    }

    [InvokedOnDeserialization]
    // ReSharper disable once UnusedMember.Global | Found via attribute and invoked using reflection
    protected DomainManagedList(List<T> itemList)
    {
        _itemList = itemList;
    }
}

请注意,Add方法具有internal访问修饰符,以防止来自任何其他程序集的代码更改列表。
由于mongoDB驱动程序代码是开源的,我搜索了它的github存储库,发现https://github.com/mongodb/mongo-csharp-driver/blob/master/src/MongoDB.Bson/Serialization/Serializers/EnumerableInterfaceImplementerSerializer.cs文件中有does not have a suitable constructor or Add method行出现在异常中。
所以错误来自EnumerableInterfaceImplementerSerializer类型。在浏览这个开源MongoDB驱动程序库时,和一点关于如何创建自定义反序列化器的Internet搜索,我得出结论,我需要一个自定义BsonSerializer并重写此反序列化终结器,该终结器应允许查找非公共方法。我们已经明白了拥有一个protected构造函数对我们来说更有意义,这个示例修复展示了这种方法;但是您可以轻松地调整代码以访问任何其他方法来实现相同的反序列化结果。
修复:我们添加了带有自定义属性的受保护构造函数(其他人不需要),以确定性地从自定义Bson序列化程序中找到此构造函数。最后,确保在应用启动时注册此序列化程序。完整代码如下。
自定义属性:

[AttributeUsage(AttributeTargets.Constructor)]
public class InvokedOnDeserializationAttribute : Attribute
{

}

自定义类型新定义:(注意:它现在包括受保护的构造函数。

public class DomainManagedList<T> : IEnumerable<T>
{
    private readonly List<T> _itemList;

    public DomainManagedList()
    {
        _itemList = new List<T>();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ((IEnumerable<T>)_itemList).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    internal void Add(T item)
    {
        _itemList.Add(item);
    }

    internal void Remove(T item)
    {

    }

    [InvokedOnDeserialization]
    // ReSharper disable once UnusedMember.Global | Found via attribute and invoked using reflection
    protected DomainManagedList(List<T> itemList)
    {
        _itemList = itemList;
    }
}

自定义BsonSerializer:

public class DomainManagedListSerializer<TItem> 
    : EnumerableInterfaceImplementerSerializer<DomainManagedList<TItem>, TItem>

{
    /// This class has custom deserializer based on suitable constructor.
    /// To use this serializer you must register it using following method
    /// BsonSerializer.RegisterGenericSerializerDefinition(typeof(DomainManagedList<>), typeof(DomainManagedListSerializer<>));

    private readonly ConstructorInfo _constructorInfo;
    
    public DomainManagedListSerializer()
    {
        _constructorInfo = typeof(DomainManagedList<TItem>)
            .GetTypeInfo().GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)
            .FirstOrDefault(ci => Attribute.IsDefined((MemberInfo) ci, typeof(InvokedOnDeserializationAttribute)));

        if (_constructorInfo != null)
        {
            var constructorParameters = _constructorInfo.GetParameters();
            if (constructorParameters.Length == 1)
            {
                if (constructorParameters[0].ParameterType == typeof(List<TItem>))
                {
                    return;
                }
            }
        }

        var message = string.Format("Type '{0}' does not have a suitable constructor that " +
                                    "implements '{1}'.",
            typeof(DomainManagedList<TItem>).FullName,
            nameof(InvokedOnDeserializationAttribute));

        throw new BsonSerializationException(message);
    }

    protected override DomainManagedList<TItem> FinalizeResult(object accumulator)
    {
        return (DomainManagedList<TItem>)_constructorInfo.Invoke(new[] { accumulator });
    }
}

注册BsonSerializer(这将在服务注册之后进行):

BsonSerializer.RegisterGenericSerializerDefinition(typeof(DomainManagedList<>), typeof(DomainManagedListSerializer<>));

相关问题