简体   繁体   English

MongoDB 中序列化和反序列化结构的解决方法

[英]Workaround for Serialize and Deserialize struct in MongoDB

In MongoDB the struct (valuetype) serialization and Deserialization is not possible, because MongoDB throws an Exception: BsonClassMapSerializer.cs line: 84.在 MongoDB 中,结构(值类型)序列化和反序列化是不可能的,因为 MongoDB 抛出一个 Exception: BsonClassMapSerializer.cs line: 84。

But I want to solve this problem generally.但我想一般解决这个问题。

Background背景

We want to create a library lets call it PolyglotPersistence.Lib.我们要创建一个库,我们称之为 PolyglotPersistence.Lib。 My "clients" use this library to save his datastuctures into Database, which can be MongoDB Azure CosomosDB, or own implemented MemoryDB and some other solution.我的“客户”使用这个库将他的数据结构保存到数据库中,可以是 MongoDB Azure CosmosDB,也可以是自己实现的 MemoryDB 和其他一些解决方案。

But the MongoDB cannot save all kind of data structure, because of the struct problem.但是MongoDB无法保存所有类型的数据结构,因为结构问题。


I found some question/answer already in Stackoverflow but these solution are not general solution.我在 Stackoverflow 中已经找到了一些问题/答案,但这些解决方案不是通用解决方案。

Example 1 How do you serialize value types with MongoDB C# serializer?示例 1 如何使用 MongoDB C# 序列化程序序列化值类型? This is not general at all, When I apply this solution, I have to create a Serialize/Deserializer for every struct.这根本不是通用的,当我应用这个解决方案时,我必须为每个结构创建一个序列化/反序列化器。 That is ok, it can be done with a generic StructSerializer<>, but still the "clients" have to register it for all struct.没关系,它可以用通用的 StructSerializer<> 来完成,但“客户端”仍然必须为所有结构注册它。 Which is not acceptable, because they are don't know where the data will be serialized (Cosmos/Mongo/Memory/etc...).这是不可接受的,因为他们不知道数据将在哪里序列化(Cosmos/Mongo/Memory/etc...)。

Example 2 Serializing Immutable Value types with Mongo C# Driver It is nearly the same solution.示例 2 使用 Mongo C# 驱动程序序列化不可变值类型这几乎是相同的解决方案。 Must register a special Serializer by "client".必须通过“客户端”注册一个特殊的序列化程序。

Example 3 Deserialize a nested struct with MongoDB C# driver They change to class, which is not a good way for us. Example 3 用MongoDB C#驱动反序列化一个嵌套的struct改成class,这对我们来说不是一个好方法。


Possible Solution 1 We create a new rule: When the "client" uses a struct in his data-structure he must be inhertit from a special base class, lets say "IStruct".可能的解决方案 1我们创建一个新规则:当“客户端”在他的数据结构中使用一个结构时,他必须从一个特殊的基类继承,比如“IStruct”。 And we register a serializer for this type, and the problem solved.我们为这种类型注册了一个序列化程序,问题就解决了。

But it is littebit uncomfortable for our clients, and not a bulletproof solution.但这对我们的客户来说有点不舒服,而且不是万无一失的解决方案。

Possible Solution 2 When the user add a new type for our library (PolyglotPersistence.Lib) then we have to walk-trough on this type recursively, and detect is there the structure in it.可能的解决方案 2当用户为我们的库(PolyglotPersistence.Lib)添加新类型时,我们必须递归地遍历该类型,并检测其中是否存在结构。 When we found it, then we have to register a serializer for this type, when it is not registered yet.当我们找到它时,我们必须为这种类型注册一个序列化程序,当它尚未注册时。

But for this solution we have to find all the struct in clients datastructure.但是对于这个解决方案,我们必须在客户端数据结构中找到所有结构。

Possible Solution 3 Register a Serializer for basetype of struct.可能的解决方案 3为结构的基本类型注册一个序列化程序。 I don't know is it exists or not.我不知道它是否存在。 But this will be best solution.但这将是最好的解决方案。 The ultimate base class for struct :) struct 的最终基类:)


So the questions are:所以问题是:

  1. Is there ultimate base class or interface for all struct originally built in c#?最初用 c# 构建的所有结构是否有最终的基类或接口?
  2. If I have System.Type how can I detect it is a struct, with hundred percent safe?如果我有 System.Type 我怎么能检测到它是一个结构,百分百安全?

Thank you all answer, and please do not mark this question to duplicate, because the already answered solution are not fit to our problem.谢谢大家的回答,请不要将此问题标记为重复,因为已经回答的解决方案不适合我们的问题。 And Please, please read the question before mark it.并且请,请在标记之前阅读问题。 Thank you谢谢

PS All comment will be appreciated :) PS 所有评论将不胜感激:)

Finally I found the solution, It is between in original solution 2 and 3.最后我找到了解决方案,它介于原始解决方案 2 和 3 之间。

The main idea is, to find all the struct in "client" data structure, and register the Special Struct Serializer for it.主要思想是,找到“客户端”数据结构中的所有结构,并为其注册特殊结构序列化程序。 The challenges are following:挑战如下:

Find all the struct type in "client" data structure在“客户端”数据结构中查找所有结构类型

It must be find recursively, even the structure is part of the collection which is hidden in the class which is covered in collections, etc.... So we had to find it in all cases.它必须递归地找到,即使结构是隐藏在集合中的类中的集合的一部分,等等......所以我们必须在所有情况下找到它。 Luckily the MongoDB Helps to find all the instance, because during serialization the MongoDB makes a recursive walk-trough on each types.幸运的是 MongoDB 有助于找到所有实例,因为在序列化期间 MongoDB 对每种类型进行递归遍历。 So we register a serialization provider which is "detect" all structure and give a special serializer for it.因此,我们注册了一个“检测”所有结构的序列化提供程序,并为其提供了一个特殊的序列化程序。

Detect the given type is struct or not检测给定的类型是否为结构体

To do this job, there was a lot of answer on StackOverflow, needer of them was perfect .为了完成这项工作,StackOverflow 上有很多答案,需要它们的人是完美的 Maybe my solution is also not perfect, but we made a union of all ideas.也许我的解决方案也不完美,但我们将所有想法结合在一起。 So we check the type is not primitive, it is not enum, but it is value-type, and not a some default struct, which has already a serializer in MongoDB.所以我们检查类型不是原始类型,不是枚举,而是值类型,而不是一些默认结构,它在 MongoDB 中已经有序列化程序。


The Codes are following:代码如下:

1, Register a serializer provider for MongoDB: 1、为MongoDB注册一个serializer provider:

BsonSerializer.RegisterSerializationProvider( new MongoDB_SerializationProvider() );

2, Implement a serializer: 2、实现一个序列化器:

class MongoDB_SerializationProvider : BsonSerializationProviderBase
{
    private static readonly object locker = new object();
    private static Dictionary<Type, MongoDB_StructSerializer> _StructSerializers;
    private static MongoDB_DecimalSerializer _DecimalSerializer;


    static MongoDB_SerializationProvider()
    {
        _StructSerializers = new Dictionary<Type, MongoDB_StructSerializer>();
        _DecimalSerializer = new MongoDB_DecimalSerializer();
    }

    public override IBsonSerializer GetSerializer( Type type, IBsonSerializerRegistry serializerRegistry )
    {
        if ( type == typeof( decimal ) )
        {
            return _DecimalSerializer;
        }
        else if ( Reflection.Info.IsStruct( type ) && type != typeof( ObjectId ) )
        {
            MongoDB_StructSerializer structSerializer = null;

            lock ( locker )
            {
                if ( _StructSerializers.TryGetValue( type, out structSerializer ) == false )
                {
                    structSerializer = new MongoDB_StructSerializer( type );
                    _StructSerializers.Add( type, structSerializer );
                }
            }

            return structSerializer;
        }
        else
        {
            return null;
        }
    }
}

The decimal part is an another interesting theme, but it is not part of the current question.小数部分是另一个有趣的主题,但它不是当前问题的一部分。 One thing we must be careful: The MongoDB ObjectId is also a struct, and we do not want to register a serializer for ObjectId-s of course.我们必须注意的一件事是:MongoDB ObjectId 也是一个结构体,我们当然不想为 ObjectId-s 注册序列化程序。 There is function in the code, which do a little magic: Reflection.Info.IsStruct( type ) Here is the code of it:代码中有一个函数,它有点神奇: Reflection.Info.IsStruct( type )下面是它的代码:

    public static bool IsStruct( Type type )
    {
        if ( IsPrimitiveType( type ) == true )
            return false;

        if ( type.IsValueType == false )
            return false;

        return true;
    }

    static public bool IsPrimitiveType( Type type )
    {
        if ( type.GetTypeInfo().IsPrimitive == true )
            return true;

        if ( type.GetTypeInfo().IsEnum == true )
            return true;

        if ( type == typeof( decimal ) )
            return true;

        if ( type == typeof( string ) )
            return true;

        if ( type == typeof( DateTime ) )
            return true;

        if ( type == typeof( DateTimeOffset ) )
            return true;

        if ( type == typeof( TimeSpan ) )
            return true;

        if ( type == typeof( Guid ) )
            return true;

        return false;
    }

3, Implement the Serializer 3、实现序列化器

It is little bit longer code, but I hope it is still understandable:它的代码有点长,但我希望它仍然可以理解:

public class MongoDB_StructSerializer : IBsonSerializer
{
    public Type ValueType { get; }

    public MongoDB_StructSerializer( Type valueType )
    {
        ValueType = valueType;
    }

    public void Serialize( BsonSerializationContext context, BsonSerializationArgs args, object value )
    {
        if ( value == null )
        {
            context.Writer.WriteNull();
        }
        else
        {
            List<MemberInfo> members = Reflection.Serialize.GetAllSerializableMembers( ValueType );

            context.Writer.WriteStartDocument();
            foreach( MemberInfo member in members )
            {
                context.Writer.WriteName( member.Name );
                BsonSerializer.Serialize( context.Writer, Reflection.Info.GetMemberType( member ), Reflection.Info.GetMemberValue( member, value ), null, args );
            }
            context.Writer.WriteEndDocument();
        }
    }

    public object Deserialize( BsonDeserializationContext context, BsonDeserializationArgs args )
    {
        BsonType bsonType = context.Reader.GetCurrentBsonType();
        if ( bsonType == BsonType.Null )
        {
            context.Reader.ReadNull();
            return null;
        }
        else
        {
            object obj = Activator.CreateInstance( ValueType );

            context.Reader.ReadStartDocument();

            while ( context.Reader.ReadBsonType() != BsonType.EndOfDocument )
            {
                string name = context.Reader.ReadName();

                FieldInfo field = ValueType.GetField( name );
                if ( field != null )
                {
                    object value = BsonSerializer.Deserialize( context.Reader, field.FieldType );
                    field.SetValue( obj, value );
                }

                PropertyInfo prop = ValueType.GetProperty( name );
                if ( prop != null )
                {
                    object value = BsonSerializer.Deserialize( context.Reader, prop.PropertyType );
                    prop.SetValue( obj, value, null );
                }
            }

            context.Reader.ReadEndDocument();

            return obj;
        }
    }
}

The magic function : Reflection.Serialize.GetAllSerializableMembers is contains some really interesting stuff, what is serializable member and what not.神奇的函数: Reflection.Serialize.GetAllSerializableMembers包含一些非常有趣的东西,什么是可序列化成员,什么不是。

    public static List<MemberInfo> GetSerializableMembers( Type type, BindingFlags bindingFlags )
    {
        List<MemberInfo> list = new List<MemberInfo>();

        FieldInfo[] fields = type.GetFields( bindingFlags );
        foreach ( FieldInfo field in fields )
        {
            if ( IsFieldSerializable( type, field ) == false )
                continue;

            list.Add( field );
        }

        PropertyInfo[] properties = type.GetProperties( bindingFlags );
        foreach ( PropertyInfo property in properties )
        {
            if ( IsPropertySerializable( type, property ) == false )
                continue;

            list.Add( property );
        }

        return list;
    }

    public static bool IsFieldSerializable( Type type, FieldInfo field )
    {
        if ( field.IsInitOnly == true )
            return false;

        if ( field.IsLiteral == true )
            return false;

        if ( field.IsDefined( typeof( CompilerGeneratedAttribute ), false ) == true )
            return false;

        if ( field.IsDefined( typeof( IgnoreAttribute ), false ) == true )
            return false;

        return true;
    }

    public static bool IsPropertySerializable( Type type, PropertyInfo property )
    {
        if ( property.CanRead == false )
            return false;

        if ( property.CanWrite == false )
            return false;

        if ( property.GetIndexParameters().Length != 0 )
            return false;

        if ( property.GetMethod.IsVirtual && property.GetMethod.GetBaseDefinition().DeclaringType != type )
            return false;

        if ( property.IsDefined( typeof( IgnoreAttribute ), false ) == true )
            return false;

        return true;
    }

Summary概括

This solutions tested well (about 15-20 different test cases), and works well.该解决方案测试良好(大约 15-20 个不同的测试用例),并且运行良好。 I think MongoDB community also able to implement the struct serialization.我认为 MongoDB 社区也能够实现结构序列化。 They sad it can not be done, because the struct are valutypes, so that is why values are copied not the reference, So when one function changes the value inside, the original not changed.他们很遗憾无法做到,因为结构是值类型,所以这就是为什么复制值而不是引用的原因,所以当一个函数更改内部值时,原始值不会更改。 But!但! All the serialization code inside the MongoDB Uses 'object' and structs are also objects. MongoDB 内部的所有序列化代码都使用“对象”,结构也是对象。 And nowhere in the driver code, there is no member changes.在驱动程序代码中,没有任何成员更改。 Only in deserialize, which is overwritten in our code.仅在反序列化中,它在我们的代码中被覆盖。

So The MongoDB community can do it, if they want it!所以 MongoDB 社区可以做到,如果他们想要的话! :) :)

PS Than you to read the long post, here is a Potato PS 比你看长的帖子,这里是土豆

补充 György Gulyás 的回答:如果您的模型具有可为空类型,只需将以下内容添加到public static bool IsStruct( Type type )作为方法的第一行:

if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) type = Nullable.GetUnderlyingType(type);

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

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