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

时间:2018-12-01 07:12:02

标签: c# mongodb serialization struct

在MongoDB中,无法进行结构(值类型)序列化和反序列化,因为MongoDB抛出异常:BsonClassMapSerializer.cs行:84。

但是我想一般解决这个问题。

背景

我们要创建一个库,将其称为PolyglotPersistence.Lib。我的“客户”使用此库将其数据结构保存到数据库中,该数据库可以是MongoDB Azure CosomosDB,也可以是自己实现的MemoryDB和其他解决方案。

但是由于结构问题,MongoDB无法保存所有类型的数据结构。


我已经在Stackoverflow中找到了一些问题/答案,但是这些解决方案不是通用解决方案。

示例1 How do you serialize value types with MongoDB C# serializer? 这根本不是一般性的,在应用此解决方案时,我必须为每个结构创建一个Serialize / Deserializer。没关系,可以使用通用的StructSerializer <>完成,但是仍然“客户端”必须为所有结构注册它。这是不可接受的,因为他们不知道数据将在何处进行序列化(Cosmos / Mongo / Memory / etc ...)。

示例2 Serializing Immutable Value types with Mongo C# Driver 这几乎是相同的解决方案。必须通过“客户端”注册特殊的序列化器。

示例3 Deserialize a nested struct with MongoDB C# driver 他们换了课,这对我们来说不是一个好方法。


可能的解决方案1 ​​ 我们创建了一条新规则:当“客户端”在其数据结构中使用结构时,他必须是特殊基类的继承者,比如说“ IStruct”。并且我们为这种类型注册了一个序列化器,问题就解决了。

但这对我们的客户来说有点不舒服,而不是防弹解决方案。

可能的解决方案2 当用户为我们的库添加新类型(PolyglotPersistence.Lib)时,我们必须递归遍历该类型,并检测其中是否有结构。找到它后,我们必须为该类型注册一个序列化器,而尚未注册。

但是对于此解决方案,我们必须在客户端数据结构中找到所有结构。

可能的解决方案3 注册一个序列化器以获取struct的基类型。我不知道它是否存在。但这将是最好的解决方案。 struct的最终基类:)


所以问题是:

  1. 最初使用c#内置的所有结构是否都有最终的基类或接口?
  2. 如果我拥有System.Type,如何检测到它是具有100%安全性的结构?

谢谢大家的回答,请不要将此问题标记为重复,因为已经回答的解决方案不适合我们的问题。并且,请在标记问题之前阅读问题。 谢谢

P.S。所有评论将不胜感激:)

2 个答案:

答案 0 :(得分:9)

最后我找到了解决方案,它在原始解决方案2和3之间。

主要思想是,在“客户端”数据结构中找到所有结构,并为其注册特殊结构串行器。挑战如下:

在“客户端”数据结构中找到所有结构类型

必须以递归方式找到它,即使结构是集合的一部分,也隐藏在集合中涵盖的类等中。...所以我们必须在所有情况下都找到它。幸运的是,MongoDB有助于查找所有实例,因为在序列化期间,MongoDB会对每种类型进行递归遍历。因此,我们注册了一个“检测”所有结构的序列化提供程序,并为其提供了特殊的序列化程序。

检测给定类型是否为struct

要完成这项工作,在StackOverflow上有很多答案,其中其中之一是完美的。也许我的解决方案也不是完美的,但是我们结合了所有想法。因此,我们检查类型不是原始类型,不是枚举,而是值类型,而不是某些默认结构,该结构在MongoDB中已经有序列化器。


代码如下:

1,为MongoDB注册一个序列化提供程序:

BsonSerializer.RegisterSerializationProvider( new MongoDB_SerializationProvider() );

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;
        }
    }
}

小数部分是另一个有趣的主题,但它不是当前问题的一部分。我们必须小心的一件事:MongoDB ObjectId也是一个结构,我们当然不想为ObjectId注册序列化器。代码中有一些功能,它们有些神奇: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,实施序列化程序

这是更长的代码,但我希望它仍然可以理解:

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;
        }
    }
}

神奇的功能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;
    }

摘要

该解决方案测试良好(大约15-20个不同的测试用例),并且运行良好。我认为MongoDB社区也能够实现struct序列化。他们感到遗憾的是无法完成,因为该结构是值类型,因此这就是为什么将值复制而不是引用的原因,因此,当一个函数更改内部的值时,原始值不会更改。但! MongoDB中所有使用'object'和structs的序列化代码也是对象。在驱动程序代码中的任何地方都没有成员更改。仅在反序列化时会被我们的代码覆盖。

因此,如果他们愿意,MongoDB社区可以做到! :)

P.S。比起您阅读长篇文章,这里是Potato

答案 1 :(得分:0)

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

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