【问题标题】:Workaround for Serialize and Deserialize struct in MongoDBMongoDB 中序列化和反序列化结构的解决方法
【发布时间】:2019-05-03 06:19:50
【问题描述】:

在 MongoDB 中,结构(值类型)序列化和反序列化是不可能的,因为 MongoDB 抛出异常:BsonClassMapSerializer.cs line: 84。

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

背景

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

但是MongoDB因为struct的问题不能保存所有类型的数据结构。


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

示例 1 How do you serialize value types with MongoDB C# serializer? 这一点都不通用,当我应用这个解决方案时,我必须为每个结构创建一个序列化/反序列化器。没关系,可以使用通用的 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 的终极基类:)


所以问题是:

  1. 所有最初用 c# 构建的结构都有最终的基类或接口吗?
  2. 如果我有 System.Type,我如何检测它是一个结构,百分百安全?

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

附:所有评论将不胜感激:)

【问题讨论】:

标签: c# mongodb serialization struct


【解决方案1】:

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

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

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

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

检测给定类型是否为结构体

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


代码如下:

1、为MongoDB注册一个serializer provider:

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-s 注册一个序列化程序。代码中有函数,有点神奇: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 社区也能够实现结构序列化。他们很难过,因为结构是值类型,所以这就是为什么值被复制而不是引用的原因,所以当一个函数改变里面的值时,原来的值没有改变。但! MongoDB 内部的所有序列化代码都使用“对象”,结构也是对象。在驱动程序代码中没有任何地方,没有成员更改。仅在反序列化中,在我们的代码中被覆盖。

所以 MongoDB 社区可以做到,只要他们愿意! :)

附:比你看长帖,这里是Potato

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 2010-10-18
      • 2013-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-31
      • 2016-06-15
      • 2010-12-11
      相关资源
      最近更新 更多