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

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

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

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


person Mike Webb    schedule 17.11.2011    source источник
comment
Вам нужно добавить элементы в класс или вы внедряете элементы в динамический объект? если это первое, вы можете просто инициализировать элементы значением по умолчанию   -  person Rune FS    schedule 18.11.2011
comment
I know that the old settings file will not deserialize, Нет, только ваши вновь добавленные поля/свойства получат значение по умолчанию (или значение, которое вы указали в конструкторе)   -  person L.B    schedule 18.11.2011
comment
XmlSerializer поддерживает [DefaultValue], но использует его во время сериализации. Во время десериализации он просто запускает конструктор. См. мой обновленный ответ, чтобы узнать, как обрабатывать значения по умолчанию на основе версий.   -  person Dustin Davis    schedule 18.11.2011


Ответы (7)


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

person McKay    schedule 17.11.2011

Из MSDN

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

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

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

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

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

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

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

Чтобы ответить на ваш вопрос любым способом, чтобы указать значения по умолчанию, вы должны использовать "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>");
  }
}

// вот результат введите здесь описание изображения

person Surjit Samra    schedule 17.11.2011
comment
Это не безопасно. Было бы лучше добавить атрибут версии в корень и проверить только номер версии, а затем использовать стратегию parrten для десериализации оттуда. Ваш пример легко сломать. - person Dustin Davis; 18.11.2011
comment
Это был всего лишь простой пример, подтверждающий возможность десериализации с новым определением класса. Я знаю об онсериализованных атрибутах, но я думал, что это не его вопрос, вот полный пример, который я сделал несколько дней назад, который проходит через WCF для обработки неинициализированных переменных-членов stackoverflow.com/questions/8084868/ - person Surjit Samra; 18.11.2011
comment
Вы навязываете решение WCF, когда в его вопросе нет упоминания о WCF. - person Dustin Davis; 18.11.2011
comment
Я не навязываю решение WCF. Это пример того, как вы можете переопределить сериализацию и десериализацию по умолчанию. Он может пропустить WCF и посмотреть, как данные класса украшены атрибутами и как переопределение десериализации помогает предоставить значения по умолчанию. - person Surjit Samra; 18.11.2011
comment
@DustinDavis Я все еще жду проверки атрибутов вашей отказоустойчивой версии и использования шаблона стратегии? Это обучающий сайт, а не просто критиковать, а потом прятать - person Surjit Samra; 18.11.2011
comment
Поисковые системы — замечательная вещь, вы должны попробовать их как-нибудь. Смотрите мой обновленный ответ. - person Dustin Davis; 18.11.2011
comment
Кому теперь нужен поисковик, если у нас есть такие мастера, как вы. Возможно, вам следует предоставить свой источник (как вы сказали, попробуйте поисковую систему) с вашим ответом, а также для получения дополнительных знаний :) - person Surjit Samra; 18.11.2011
comment
если у вас есть вопросы, комментарии или опасения по поводу моего ответа, я буду более чем счастлив ответить на них. Владелец? Нет. - person Dustin Davis; 18.11.2011
comment
У меня будет, как только я смогу его запустить. Может быть, если вы сможете предоставить полное решение, где вы сначала сериализуете, так как в данный момент он пытается найти файл в моей системе, который не существует по очевидной причине :( - person Surjit Samra; 18.11.2011
comment
Обновлен мой ответ в соответствии с рекомендациями Microsoft MSDN здесь msdn .microsoft.com/en-us/library/ms229752(v=vs.80).aspx - person Surjit Samra; 19.11.2011
comment
Хороший, надо будет проверить этот атрибут. Но вы все еще не решаете проблему OP, предоставляя значения по умолчанию при десериализации старых значений. XmlSerializer десериализует модель, даже если в ней есть элементы, которые не отображаются. - person Dustin Davis; 19.11.2011
comment
Вы проверяли результаты Test1 - person Surjit Samra; 19.11.2011

Вам нужно вручную обработать его с помощью пользовательских методов и пометить их соответствующими атрибутами 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();
        }
person Dustin Davis    schedule 17.11.2011
comment
Может быть, вы можете предоставить образец номера версии для проверки, а затем использовать шаблон стратегии. - person Surjit Samra; 18.11.2011
comment
вам не нужно в большинстве случаев, однако вы можете сделать это и таким образом. - person vittore; 18.11.2011
comment
Не могли бы вы написать шаги, как его использовать. Как и в случае с текущей настройкой, она возвращается с необработанной версией по очевидной причине, поскольку я не думаю, что вы проверяли ее самостоятельно. - person Surjit Samra; 18.11.2011
comment
Вставил не ту версию модели. Это должна была быть строка, а не int. - person Dustin Davis; 18.11.2011
comment
@DustinDavis Хотя я вижу, чего вы пытаетесь достичь, но можете ли вы опубликовать свои ссылки, которые предполагают, что это лучший способ. Я до сих пор не могу найти какие-либо ресурсы, которые выступают за контроль версии так, как вы предлагаете. Ниже приведены рекомендации, предложенные Microsoft msdn.microsoft. com/en-us/library/ms229752(v=vs.80).aspx - person Surjit Samra; 19.11.2011
comment
@Surjit Ты упускаешь суть. OP хочет установить значения по умолчанию для вновь добавленных участников. Мне жаль, что вы не понимаете код. Вы можете начать с них, чтобы набрать скорость. en.wikipedia.org/wiki/Strategy_pattern и amazon.com/3-0-Design-Patterns-Judith-Bishop/dp/059652773X/ - person Dustin Davis; 19.11.2011
comment
@Surjit Кроме того, я никогда не говорил, что это лучший способ, поскольку есть много других способов, которыми он мог бы воспользоваться, например, установить значения по умолчанию в конструкторе без учета версий. Затем процесс десериализации заполнит только те элементы, которые он может. Нет необходимости в дополнительной сложности. Мое решение - лучший способ решить проблему OP по сравнению с предоставленным вами решением, которое не решает проблему значений по умолчанию. Ваше решение не будет работать должным образом, если процесс сериализации изменит порядок элементов, что легко сделать с помощью [XmlElement(Order = 99)] - person Dustin Davis; 19.11.2011
comment
@DustinDavis, возможно, вам нужно быть осторожным при написании комментариев, как будто вы не понимаете код, и вам не следует навязывать шаблоны только ради этого. - person Surjit Samra; 19.11.2011
comment
@DustinDavis Я думаю, вы неправильно поняли мой вопрос, когда я попросил опубликовать ссылки, я имею в виду, рекомендовано ли ваше решение в отрасли, я не хочу ссылаться на книгу шаблонов дизайна, я знаю достаточно шаблонов дизайна, чтобы написать свою собственную книгу :). Мне понравился ваш XmlElement (Order = 99), не могли бы вы опубликовать несколько образцов, пожалуйста. - person Surjit Samra; 19.11.2011

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

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

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

person Alex Norcliffe    schedule 17.11.2011
comment
я думаю, что вы слишком усложняете вещи, используя DataContracts и явную маркировку того, какие члены должны быть сериализованы, обработка версий контрактов данных превращается в кусок пирога. - person vittore; 18.11.2011

Используйте [System.ComponentModel.DefaultValueAttribute] для определения значений по умолчанию для сериализации.

Пример из MSDN:

private bool myVal=false;

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

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

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

person oberfreak    schedule 18.11.2011

Вы можете использовать ExtendedXmlSerializer. Этот сериализатор поддерживает десериализацию старой версии xml. Вот пример десериализации старой версии xml.

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

person Wojtpl2    schedule 22.09.2016

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

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

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

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

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

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

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

person vittore    schedule 18.11.2011