Совместимость десериализации

Я пытаюсь десериализовать "SomeClass" с более старой версией приложения. Я получаю это ниже исключения

System.Runtime.Serialization.SerializationException: ObjectManager обнаружил недопустимое количество исправлений. Это обычно указывает на проблему в Formatter.

Отсериализация выдает исключение, когда я сериализует версию 0,9 и пытается десериализоваться с использованием версии 0.8. Я думал, что атрибут OptionalField выполнит трюк, но это не так.

// Version 0.8
[Serializable()]
class Foo{
  Bar b;
} 

// Version 0.9
[Serializable()]
class Foo{
  Bar b;
  [OptionalField]
  Zoo z;
}

Учитывая, что я не могу изменить версию 0.8, как мне добавить большее состояние в объект Foo, чтобы предыдущие версии могли десериализовать все, что они могут?

Любой указатель будет действительно оценен.

Обновление 1 Бар и зоопарк - это другие классы, которые являются сериализуемыми и содержат Hashtables и другие сериализуемые материалы. В этих классах все сериализуется. Кроме того, у меня нет никаких расположений.

Ответы

Ответ 1

Во-первых, никогда НИКОГДА не используйте функции сериализации CLR для всего, что напоминает долговременное хранение. Мы делаем эту ошибку обычно один раз, ставим объекты в поле базы данных blob и поглаживаем себя в спину, думая, что мы умны. И тогда CLR получает патч, или наши сборки меняют версии, и вы ввернуты. Так что не делайте этого.

Если вы все еще хотите это сделать, лучший способ решить проблему - создать свой собственный SerializationBinder, который выглядит примерно так:

public sealed class CustomBinder : SerializationBinder {

    public override Type BindToType(string assemblyName, string typeName) {

        Type typeToDeserialize = null;

        if (typeName.IndexOf("SomeType") != -1) {
            typeToDeserialize = typeof(Foo.Bar.Bax.NewType);
        }
        else if (typeName.IndexOf("SomeOtherType") != -1) {
            typeToDeserialize = typeof(Foo.Bar.Bax.SomeOtherNewType);
        }
        else {
            // ... etc
        }

        return typeToDeserialize;
    }
}

Задайте свойство Binder используемого форматирования перед десериализацией, чтобы он переопределял значения по умолчанию.

Обратите внимание, что здесь я не предлагаю выпадающее решение, я рекомендую, как решить проблему. После того, как вы преобразитесь из того, что вы делаете, изучите другие технологии сериализации, такие как protobuf, или напишите сами. В любом случае вы никогда не должны полагаться на CLR для долгосрочной поддержки сериализации.

Ответ 2

Если конструкторы для каждой версии совместимы (например, есть конструктор без параметров или Foo(Bar b) для обеих версий), вы можете вызвать

BinaryFormatter formatter = new BinaryFormatter();
formatter.AssemblyFormat = Formatters.FormatterAssemblyStyle.Simple;

Перед десериализацией потока.

Ответ 3

В качестве консультанта для людей, исследующих этот вопрос "пока не стало слишком поздно"... Я настоятельно рекомендую не настаивать на использовании BinaryFormatter. Это нормально для переходной передачи между двумя доменами приложения, которые находятся в синхронизации, но это касается IMO. Существуют другие инструменты сериализации, которые не имеют этих проблем. В терминах двоичных файлов protobuf-net - довольно разумный вариант - позволяющий добавлять/удалять/переименовывать и т.д. Без боли.

Ответ 4

Кажется, что одним из способов сделать это будет иметь объект с версией, таким образом вы можете попробовать десериализовать объект, используя последнюю версию. Если это не сработало, отпустите версию до тех пор, пока она не будет успешной. Затем, как только у вас есть объект, обновите его до последней версии объекта и используйте значения по умолчанию для любых полей, для которых у вас нет данных.

Ответ 5

Атрибут optional field должен был сделать трюк. Можете ли вы опубликовать фактические классы, которые вы пытаетесь сериализовать.

Сначала вы можете попробовать эти вещи -

конвертировать structs, если таковые имеются, в classes

попробуйте Soap Serialization вместо binary serilization