Обходной путь для сериализации и десериализации структуры в MongoDB - PullRequest
0 голосов
/ 01 декабря 2018

В MongoDB сериализация структуры (valuetype) и десериализация невозможны, поскольку MongoDB генерирует исключение: BsonClassMapSerializer.cs строка: 84.

Но я хочу решить эту проблему в целом,

Фон

Мы хотим создать библиотеку, назовем ее PolyglotPersistence.Lib.Мои «клиенты» используют эту библиотеку для сохранения своих данных в базе данных, которая может быть MongoDB Azure CosomosDB или собственной реализованной MemoryDB и каким-либо другим решением.

Но MongoDB не может сохранить все виды структуры данных из-заstruct problem.


Я нашел какой-то вопрос / ответ уже в Stackoverflow, но это решение не является общим решением.

Пример 1 Как вы сериализуететипы значений с MongoDB C # serializer? Это вообще не является общим. Когда я применяю это решение, я должен создать Serialize / Deserializer для каждой структуры.Это нормально, это можно сделать с помощью стандартного StructSerializer <>, но «клиенты» должны зарегистрировать его для всей структуры.Что неприемлемо, потому что они не знают, где будут сериализованы данные (Космос / Монго / Память / и т. Д.).

Пример 2 Сериализация типов неизменяемых значений с драйвером Mongo C # Это почти то же самое решение.Необходимо зарегистрировать специальный сериализатор с помощью «клиента».

Пример 3 Десериализация вложенной структуры с драйвером MongoDB C # Они переходят в класс, что не является хорошим способом дляus.


Возможное решение 1 Мы создаем новое правило: когда «клиент» использует структуру в своей структуре данных, он должен наследоваться от специального базового класса,сказать "IStruct".И мы регистрируем сериализатор для этого типа, и проблема решена.

Но это неудобно для наших клиентов, а не пуленепробиваемое решение.

Возможное решение 2 Когда пользователь добавляет новый тип для нашей библиотеки (PolyglotPersistence.Lib), мы должны рекурсивно пройтись по этому типу и определить, есть ли в нем структура.Когда мы его нашли, мы должны зарегистрировать сериализатор для этого типа, когда он еще не зарегистрирован.

Но для этого решения мы должны найти всю структуру в клиентской структуре данных.

Возможное решение 3 Зарегистрировать сериализатор для базового типа структуры.Я не знаю, существует это или нет.Но это будет лучшим решением.Конечный базовый класс для struct:)


Итак, вопросы:

  1. Существует ли базовый базовый класс или интерфейс для всей структуры, изначально встроенной в c #?
  2. Если у меня есть System.Type, как я могу определить, что это структура со стопроцентной безопасностью?

Спасибо всем за ответ, и, пожалуйста, не отмечайте этот вопрос для дублирования, потому что уже ответилРешение не подходит для нашей проблемы.И, пожалуйста, прочитайте вопрос, прежде чем пометить его.Спасибо

PS Все комментарии будут оценены по достоинству:)

1 Ответ

0 голосов
/ 07 декабря 2018

Наконец-то я нашел решение, оно находится между исходным решением 2 и 3.

Основная идея состоит в том, чтобы найти всю структуру в структуре данных «клиента» и зарегистрировать для нее специальный сериализатор структуры.,Задачи заключаются в следующем:

Найти все типы структуры в структуре данных "клиента"

Необходимо найти рекурсивно, даже если структура является частью коллекции, которая являетсяспрятан в классе, который покрыт коллекциями и т. д. Так что нам нужно было найти его во всех случаях.К счастью, MongoDB помогает найти все экземпляры, потому что во время сериализации MongoDB делает рекурсивное прохождение по каждому типу.Поэтому мы регистрируем провайдера сериализации, который «обнаруживает» всю структуру и предоставляет для него специальный сериализатор.

Обнаружить, что данный тип является структурой или нет

Для этогоработа, было много ответов на StackOverflow, из них идеально было .Возможно, мое решение тоже не идеальное, но мы объединили все идеи.Таким образом, мы проверяем, что тип не является примитивным, это не enum, но это тип значения, а не некоторая структура по умолчанию, которая уже имеет сериализатор в 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;
        }
    }
}

Десятичная часть - еще одна интересная тема, но она не является частью текущего вопроса.Мы должны быть осторожны с одной вещью: ObjectId MongoDB также является структурой, и мы, конечно, не хотим регистрировать сериализатор для 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 также может реализовать сериализацию структуры.Они печальны, что это не может быть сделано, потому что структура - это valutypes, поэтому значения копируются не по ссылке, поэтому, когда одна функция изменяет значение внутри, оригинал не изменяется.Но!Весь код сериализации внутри MongoDB использует «объект» и структуры также являются объектами.И нигде в коде драйвера нет изменений членов.Только в десериализации, которая перезаписана в нашем коде.

Так что сообщество MongoDB может сделать это, если они этого захотят!:)

PS Чем читать длинный пост, здесь есть Картошка

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...