Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

От MSDN

Лучшие рекомендации Чтобы обеспечить правильное поведение версий, следуйте этим правилам при изменении типа версии с версии на версию:

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

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

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

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

Test1 предполагает сериализацию со старым определением класса Root с помощью только одного элемента Element1

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>");
  }
}

//здесь выводится enter image description here

Ответ 3

Вам необходимо вручную обработать его с помощью пользовательских методов и пометить их соответствующими атрибутами 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();
        }

Ответ 4

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

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

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

Ответ 5

Используйте [System.ComponentModel.DefaultValueAttribute], чтобы определить DefaultValues ​​для сериализации.

Пример из MSDN:

private bool myVal=false;

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

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

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

Ответ 7

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

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

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

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

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

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

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