В C# как десериализовать XML из старого объекта в обновленный объект и игнорировать отсутствующие элементы XML?

У меня есть файл пользовательских настроек, который я сериализую / десериализую, используя XmlSerializer, У меня нет определенной схемы и тегов сериализации в моем определении объекта, просто прямая сериализация объекта (хотя я добавлю их при необходимости).

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

Есть ли способ указать значения по умолчанию для добавленных членов или какой-то простой способ игнорировать их, если они отсутствуют в XML?

7 ответов

Решение

Он должен десериализоваться очень хорошо, он просто использует конструктор по умолчанию для инициализации элементов. Так что они останутся без изменений.

Из MSDN

Рекомендации Для обеспечения правильного поведения версий придерживайтесь следующих правил при изменении типа с версии на версию:

  • При добавлении нового сериализованного поля примените атрибут OptionalFieldAttribute.

  • При удалении атрибута NonSerializedAttribute из поля (которое не было сериализуемым в предыдущей версии), примените атрибут OptionalFieldAttribute.

  • Для всех необязательных полей установите значимые значения по умолчанию, используя обратные вызовы сериализации, если 0 или ноль в качестве значений по умолчанию не являются приемлемыми.

Я попытался смоделировать ваш случай, когда в новой версии класса появился новый член с именем Element2. инициализировал моего нового члена на "Это новый участник", вот полное доказательство

Test1 предполагает, что вы сериализовали со старым определением класса Root только один элемент 1

Test2, когда вы сериализовали и де сериализовали с новым определением Root Class

Чтобы ответить на ваш вопрос любым способом, чтобы предоставить значения по умолчанию, вы должны использовать "OptionalField"

using System;
using System.Runtime.Serialization;
using System.IO;

public class Test
{

  [Serializable]
  public class Root
  {
    [OptionalField(VersionAdded = 2)] // As recommended by Microsoft
    private string mElement2 = "This is new member";
    public String Element1 { get; set; }    
    public String Element2 { get { return mElement2; } set { mElement2 = value; } }
  }

  public static void Main(string[] s)
  {
    Console.WriteLine("Testing serialized with old definition of Root ");
    Console.WriteLine(" ");
    Test_When_Original_Object_Was_Serialized_With_One_Element();
    Console.WriteLine(" ");
    Console.WriteLine("Testing serialized with new definition of Root ");
    Console.WriteLine(" ");
    Test_When_Original_Object_Was_Serialized_With_Two_Element();
    Console.ReadLine();
  }

  private static void TestReadingObjects(string xml)
  {
    System.Xml.Serialization.XmlSerializer xmlSerializer =
    new System.Xml.Serialization.XmlSerializer(typeof(Root));


    System.IO.Stream stream = new MemoryStream();
    System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
    Byte[] bytes = encoding.GetBytes(xml);
    stream.Write(bytes, 0, bytes.Length);
    stream.Position = 0;
    Root r = (Root)xmlSerializer.Deserialize(stream);

    Console.WriteLine(string.Format("Element 1 = {0}", r.Element1));

    Console.WriteLine(string.Format("Element 2 = {0}", r.Element2 == null ? "Null" : r.Element2));
  }
  private static void Test_When_Original_Object_Was_Serialized_With_One_Element()
  {
    TestReadingObjects(@"<Root>   <Element1>1</Element1>   </Root>");
  }

  private static void Test_When_Original_Object_Was_Serialized_With_Two_Element()
  {
    TestReadingObjects(@"<Root>   <Element1>1</Element1> <Element2>2</Element2>   </Root>");
  }
}

// здесь вывод

Вам необходимо вручную обработать его с помощью пользовательских методов и пометить их соответствующими атрибутами OnSerializing/OnSerialized/OnDeserializing/OnDeserialized и вручную определить, как инициализировать значения (если это возможно)

http://msdn.microsoft.com/en-us/library/system.runtime.serialization.ondeserializingattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.ondeserializedattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.onserializedattribute.aspx http://msdn.microsoft.com/en-us/library/system.runtime.serialization.onserializingattribute.aspx

лучший способ - определить версию заранее и использовать шаблон стратегии для десериализации. Это не всегда возможно, поэтому используйте то, что я предлагаю в этом случае.

Обновление: предыдущий ответ применим только к двоичной сериализации. Для обычного XML вы можете использовать этот метод.

class Program
    {
        static void Main(string[] args)
        {
            Deserialize(@"..\..\v1.xml");
        }

        private static Model Deserialize(string file)
        {
            XDocument xdoc = XDocument.Load(file);
            var verAtt = xdoc.Root.Attribute(XName.Get("Version"));
            Model m = Deserialize<Model>(xdoc);

            IModelLoader loader = null;

            if (verAtt == null)
            {
                loader = GetLoader("1.0");
            }
            else
            {
                loader = GetLoader(verAtt.Value);
            }

            if (loader != null)
            {
                loader.Populate(ref m);
            }
            return m;
        }

        private static IModelLoader GetLoader(string version)
        {
            IModelLoader loader = null;
            switch (version)
            {
                case "1.0":
                    {
                        loader = new ModelLoaderV1();
                        break;
                    }
                case "2.0":
                    {
                        loader = new ModelLoaderV2();
                        break;
                    }
                case "3.0": { break; } //Current
                default: { throw new InvalidOperationException("Unhandled version"); }
            }
            return loader;
        }

        private static Model Deserialize<T>(XDocument doc) where T : Model
        {
            Model m = null;
            using (XmlReader xr = doc.CreateReader())
            {
               XmlSerializer xs = new XmlSerializer(typeof(T));
               m = (Model)xs.Deserialize(xr);
               xr.Close();
            }
            return m;
        }
    }

    public interface IModelLoader
    {
        void Populate(ref Model model);
    }

    public class ModelLoaderV1 : IModelLoader
    {
        public void Populate(ref Model model)
        {
            model.City = string.Empty;
            model.Phone = "(000)-000-0000";
        }
    }

    public class ModelLoaderV2 : IModelLoader
    {
        public void Populate(ref Model model)
        {
            model.Phone = "(000)-000-0000";
        }
    }

    public class Model
    {
        [XmlAttribute(AttributeName = "Version")]
        public string Version { get { return "3.0"; } set { } }
        public string Name { get; set; } //V1, V2, V3
        public string City { get; set; } //V2, V3
        public string Phone { get; set; } //V3 only

    }

В зависимости от ваших требований это можно упростить до единого метода на модели (или на загрузчике модели).

Это также может быть использовано для проверки модели после десериализации.

Изменить: вы можете сериализовать, используя следующий код

 private static void Serialize(Model model)
        {
            XmlSerializer xs = new XmlSerializer(typeof(Model));
            FileStream f = File.Create(@"..\..\v1.xml");
            xs.Serialize(f, model);

            f.Close();
        }

Если вы следуете этому шаблону, это довольно просто:

  • Выполните сериализацию / десериализацию самостоятельно, внедрив ISerializable
  • Используйте это для сериализации как членов вашего объекта, так и номера версии сериализации.
  • В коде десериализации запустите оператор switch-case для номера версии. Когда вы начнете, у вас будет только одна версия - начальный код десериализации. По мере продвижения вы помечаете новый номер версии во вновь сериализованных снимках.
  • Для будущих версий вашего объекта всегда оставляйте существующий код десериализации нетронутым или изменяйте его так, чтобы он отображался на элементы, которые вы переименовываете / реорганизуете, и в первую очередь просто добавляйте новый оператор case для новой версии сериализации.

Таким образом, вы сможете успешно десериализовать предыдущие данные, даже если снимок сериализации был создан из предыдущей версии вашей сборки.

Использовать [System.ComponentModel.DefaultValueAttribute] определить DefaultValues ​​для сериализации.

Пример из MSDN:

private bool myVal=false;

[DefaultValue(false)]
 public bool MyProperty {
    get {
       return myVal;
    }
    set {
       myVal=value;
    }
 }

Поэтому, если вы DeSerialize и свойство не заполнено, оно будет использовать defaultValue в качестве значения, и вы можете использовать свой старый XML для генерации нового объекта.

Если в новой версии были удалены свойства, это должно пройти без проблем через XMLSerialization. (насколько я знаю)

Вы можете использовать https://github.com/wojtpl2/ExtendedXmlSerializer. Этот сериализатор поддерживает десериализацию старой версии xml. Вот пример десериализации старой версии XML

Вы даже можете прочитать разные версии объекта из одного файла.

.NET предоставляет достаточно много для сериализации / десериализации и управления версиями.

1) пользовательские атрибуты DataContract / DataMember и DataContractSerializer

2) согласно MSDN эти изменения ломаются

  • Изменение значения имени или пространства имен контракта данных.
  • Изменение порядка членов данных с помощью свойства Order в DataMemberAttribute.
  • Переименование члена данных.
  • Изменение контракта данных члена данных.

3) Когда тип с дополнительным полем десериализуется в тип с пропущенным полем, дополнительная информация игнорируется.

4) Когда тип с отсутствующим полем десериализуется в тип с дополнительным полем, дополнительное поле остается с его значением по умолчанию, обычно равным нулю или нулю.

5) Рассмотрите возможность использования IExtensibleDataObject для управления версиями.

Другие вопросы по тегам