简体   繁体   English

将对象反序列化为与 MongoDB C# 驱动程序的接口

[英]Deserialize object as an interface with MongoDB C# Driver

I am developing a project that uses MongoDB (with C# driver) and DDD .我正在开发一个使用MongoDB (带有 C# 驱动程序)和DDD 的项目

I have a class ( aggregate ) which have a property which type is an interface.我有一个类(聚合),它有一个类型是接口的属性。 In another class, I have implemented this interface.在另一个类中,我已经实现了这个接口。 This class has another property which type is an interface and is setted with another implemented class.这个类有另一个属性,它是一个接口类型,并用另一个实现的类设置。

The code below explains better:下面的代码解释得更好:

// Interfaces
public interface IUser {
    Guid Id { get; set;}
    IPartner Partner{ get; set; }
}

public interface IPartner {
    IPhone Mobile { get; set; }
}

public interface IPhone {
    string number { get; set; }
}

// Implemented Classes
public class User: IUser {
    [BsonId(IdGenerator = typeof(GuidGenerator))]
    public Guid Id { get; set; }

    [BsonIgnoreIfNull]
    public IPartner Partner { get; set; }
}

public struct Partner : IPartner {
    public IPhone Mobile { get; set; }
}

public struct Phone : IPhone {
    public string Number { get; set; }
}

Well, when I call the MongoCollection<User>.Insert() method, it throws two exceptions:好吧,当我调用MongoCollection<User>.Insert()方法时,它抛出两个异常:

System.IO.FileFormatException: An error occurred while deserializing the Partner property of class .User: An error occurred while deserializing the Phone property of class .Partner: Value class .Mobile cannot be deserialized. System.IO.FileFormatException:反序列化类 .User 的 Partner 属性时出错:反序列化类 .Partner 的 Phone 属性时出错:值类 .Mobile 无法反序列化。 ---> System.IO.FileFormatException: An error occurred while deserializing the Mobile property of class .Partner: Value class .Phone cannot be deserialized. ---> System.IO.FileFormatException: 反序列化类 .Partner 的 Mobile 属性时出错:无法反序列化值类 .Phone。 ---> MongoDB.Bson.BsonSerializationException: Value class .Phone cannot be deserialized. ---> MongoDB.Bson.BsonSerializationException:值类 .Phone 无法反序列化。

Then, I searched the internet for discover how to deserialize the type as an interface, and I think I have to ways to do it: mapping the property with a cast, using the BsonClassMap.RegisterClassMap or writing a custom BSON serializer.然后,我在互联网上搜索发现如何将类型反序列化为接口,我想我必须有办法做到这一点:使用BsonClassMap.RegisterClassMap映射属性,使用BsonClassMap.RegisterClassMap或编写自定义 BSON 序列化程序。

I need to know which of these two ways is better and how to implement it.我需要知道这两种方式中哪一种更好以及如何实现它。

Note : I need a solution that does not modify the interfaces, because theirs project cannot contain any external reference.注意:我需要一个不修改接口的解决方案,因为他们的项目不能包含任何外部引用。

Well, I have found a lot of problems when trying to get this answer. 好吧,我在试图得到这个答案时发现了很多问题。

First of all, the MongoDB C# Driver, does have some problems when deserializing interfaces , like said by Craig Wilson in this question comments, and as described in the issue page . 首先,MongoDB C#驱动程序在反序列化接口时确实存在一些问题,如Craig Wilson在此问题评论中所述,并且如问题页面中所述

The secure implementation for this problem, like I said before, really may be a custom BSON serializer or a specific class map, using BsonClassMap.RegisterClassMap . 像我之前所说的,这个问题的安全实现可能是使用BsonClassMap.RegisterClassMap的自定义BSON序列化程序或特定的类映射。

So, I have implemented the class map and the problem persisted. 所以,我已经实现了类映射并且问题仍然存在。

Looking forward with the problem, I have found that exception is related to another issue of the driver: the problem when deserializing structs . 期待这个问题,我发现异常与驱动程序的另一个问题有关:反序列化structs时的问题

I have rolled back the project to the initial state (without classes map or custom serializers) and changed the struct type to class type, and it worked . 我已将项目回滚到初始状态(没有类映射或自定义序列化器)并将结构类型更改为类类型, 并且它可以工作

In resume, this exception error is related to structs deserialization, not with interfaces deserialization. 在恢复中,此异常错误与结构反序列化有关,而与反序列反序列化无关。


Anyway, it is a real problem, and the second issue needs to be considered more a bug than a improvement, like the first issue is. 无论如何,这是一个真正的问题,第二个问题需要被视为一个错误而不是改进,就像第一个问题一样。

You can find the issues at these links: 您可以在以下链接中找到问题:

[BsonSerializer(typeof(ImpliedImplementationInterfaceSerializer<IReviewExpert, ReviewExpert>))] public IReviewExpert Expert { get; set; }

适合我

We are on the 1.x branch of mongo drivers and that sadly doesn't have the ImpliedImplementationInterfaceSerializer that was suggested by Robert Baker which seems to be otherwise a good solution. 我们在mongo驱动程序的1.x分支上,遗憾的是没有Robert Baker建议的ImpliedImplementationInterfaceSerializer ,这似乎是一个很好的解决方案。 To this end I created my own serializer that allows you to specify a confcrete type for an interface member. 为此,我创建了自己的序列化程序,允许您为接口成员指定confcrete类型。

public class ConcreteTypeSerializer<TInterface, TImplementation> : BsonBaseSerializer where TImplementation : TInterface
{
    private readonly Lazy<IBsonSerializer> _lazyImplementationSerializer;

    public ConcreteTypeSerializer()
    {
        var serializer = BsonSerializer.LookupSerializer(typeof(TImplementation));

        _lazyImplementationSerializer = new Lazy<IBsonSerializer>(() => serializer);
    }

    public override object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
    {
        if (bsonReader.GetCurrentBsonType() == BsonType.Null)
        {
            bsonReader.ReadNull();
            return default(TInterface);
        }
        else
        {
            return _lazyImplementationSerializer.Value.Deserialize(bsonReader, nominalType, typeof(TImplementation), options);
        }
    }

    public override void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
    {
        if (value == null)
        {
            bsonWriter.WriteNull();
        }
        else
        {
            var actualType = value.GetType();
            if (actualType == typeof(TImplementation))
            {
                _lazyImplementationSerializer.Value.Serialize(bsonWriter, nominalType, (TImplementation)value, options);
            }
            else
            {
                var serializer = BsonSerializer.LookupSerializer(actualType);
                serializer.Serialize(bsonWriter, nominalType, value, options);
            }
        }
    }
}

Usage is as follows: 用法如下:

[BsonSerializer(typeof(ConcreteTypeSerializer<IMyInterface,MyClass>))]
public IMyInterface MyProperty {get; set;}

A few notes on the code - all it really does is lazily loads the serializer for the appropriate concrete type and then passes on all the serialize/deserialize calls to that with the appropriate concrete type instead of the interface. 关于代码的一些注意事项 - 它真正做的就是懒惰地为相应的具体类型加载序列化程序,然后将所有序列化/反序列化调用传递给具有适当具体类型而不是接口的序列化。

It also checks that the type is actually of the expected type and if not just finds the default serializer for the type. 它还会检查该类型实际上是否为预期类型,如果不是,则只查找该类型的默认序列化程序。

Registering one concrete serializer did not work for me as the object I wanted to store included a list of an interface that had like 6 different implementations.注册一个具体的序列化程序对我不起作用,因为我想存储的对象包含一个接口列表,该列表具有 6 个不同的实现。 Using CosmosDB this stored fine with some Newtonsoft settings.使用 CosmosDB 可以通过一些 Newtonsoft 设置保存得很好。 It serialized the type of the implementation into the object and called it $type .它将实现的类型序列化到对象中并将其称为$type So, I figured I´ll just do the same to make it work in MongoDB.所以,我想我会做同样的事情让它在 MongoDB 中工作。

public class BsonTypeSerializer<T> : IBsonSerializer<T>
{
    public Type ValueType { get => typeof(T); }

    public T Deserialize(
        BsonDeserializationContext context, 
        BsonDeserializationArgs args)
    {
        var document = BsonSerializer.Deserialize<BsonDocument>(context.Reader);
        var typeStr = document.GetValue("$type").AsString;
        var type = Type.GetType(typeStr);
        var result = (T) BsonSerializer.Deserialize(document, type);
        return result;
    }

    public void Serialize(
        BsonSerializationContext context, 
        BsonSerializationArgs args,
        T value)
    {
        var typeStr = value.GetType().FullName;
        BsonDocument document = value.ToBsonDocument();
        document.Add(new BsonElement("$type", BsonValue.Create(typeStr)));
        BsonSerializer.Serialize(context.Writer, typeof(BsonDocument), document);
    }

    public void Serialize(
        BsonSerializationContext context,
        BsonSerializationArgs args,
        object value)
        => Serialize(context, args, (T) value);

    object IBsonSerializer.Deserialize(
        BsonDeserializationContext context,
        BsonDeserializationArgs args)
        => Deserialize(context, args);
}

You would give this your interface as T when registering it.注册时,您可以将此接口指定为T The concrete type will be stored in the Document.具体类型将存储在文档中。

This would however break if you refractor your class names or even the namespace they are in. So, not a good idea to store everything like this.但是,如果您折射类名或什至它们所在的命名空间,这将中断。因此,像这样存储所有内容并不是一个好主意。 I´ve used it for a one off configuration file.我已经将它用于一次性配置文件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM