Скажем, у меня есть некоторый класс, который сериализуется в xml-документ. После чего структура класса по какой-либо причине должна измениться. Но так же необходимо сохранить возможность загрузки из xml-документов старого формата. Какие решения данной проблемы существуют? Конвертация старых документов в новый формат?
Десериализация объекта после изменений в его структуре
Лучший ответ:
Можно использовать XSLT для преобразования всех вариантов старых форматов к новому. То есть, для каждай новой версии создается файл, описывающий преобразования, которые необходимо произвести для конвертации xml предыдущей версии к текущей. Таким образом, если нужно конвертировать файл версии 2 к текущей версии 5, то необходимо выполнить цепочку преобразований 2->3->4->5, где каждое преобразование осуществляется соответствующим XSLT-файлом.
Новые ответы
В MSDN описано понятие Version Tolerant Serialization (VTS), которое обозначает сериализацию, «терипимую» к изменению версии.
Version Tolerant Serialization — это набор фич, включенных в .NET Framework 2.0, которые облегчают внесение изменений в сериализуемые классы. VTS доступно для классов, помеченных атрибутом
SerializableAttribute
. Благодаря VTS можно добавлять новые поля к классам без нарушения совместимости с сериализованными данными предыдущих версий. Фичи VTS работают когда используетсяBinaryFormatter
и частично работают дляSoapFormatter
.
В частности VTS позволяет добавить свойства в класс и при этом:
- старые версии приложения смогут работать с новой версией сериализованного класса, игнорируя неизвестное поле (Tolerance of Extraneous or Unexpected Data).
- новые версии приложения смогут десериализовать старые классы, задавая пропущенным свойствам значения по умолчанию (Tolerance of Missing Data).
Пункт 1 работает из коробки, а вот для пункта 2 нужно пометить соответствующее поле атрибутом OptionalFieldAttribute
:
[Serializable]
public class Customer
{
public string FirstName;
public string LastName;
[OptionalField]
public string Country;
}
Атрибут OptionalFieldAttribute
имеет свойство VersionAdded
, которое хранит порядковый номер версии файла (начиная с 2). Каждый раз при добавлении нового свойства в класс нужно помечать это поле атрибутом OptionalFieldAttribute
и указывать версию изменения.
Если при десериализации не удается найти значение какого-то из свойств/полей, то этому свойству/полю присваивается значение по умолчанию. Чтобы изменить такое поведение нужно использовать специальные атрибуты, позволяющие реагировать на события (де)сериализации:
- OnDeserializingAttribute — отмечается метод, который будет вызван перед десериализацией;
- OnDeserializedAttribute — отмечается метод, который будет вызван после десериализацией;
- OnSerializingAttribute — отмечается метод, который будет вызван перед сериализацией;
- OnSerializedAttribute — отмечается метод, который будет вызван после сериализацией;
Пример использования:
[Serializable]
public class Customer
{
public string FirstName;
public string LastName;
[OptionalField]
public string Country;
[OnDeserializing]
private void ResetCountry (StreamingContext sc)
{
Country = "Россия";
}
}
У описанного подхода есть серьезные недостатки: во-первых, он работает только с бинарной сериализацией (поправьте если тут я ошибаюсь), и во-вторых, если сериализуется сложный граф объектов, структура которого меняется от версии к версии, то средств VTS становится явно недостаточно.
Есть статья, где описывается создание сериализатора, который достаточно терпимо относится к:
- Добавлению/удалению полей и свойств
- Изменению названий пространств имен и перемещению кода из одного пространства имен в другое.
- Изменению типов свойств и полей
Но это не является ответом на вопрос об организации процесса загрузки новой структуры из старых xml-файлов.