В C# как десериализовать XML из более старого объекта в обновленный объект и игнорировать отсутствующие элементы xml?
У меня есть файл пользовательских настроек, что я сериализовать/десериализовать с помощью XmlSerializer
. У меня нет определенной схемы и тегов сериализации в моем определении объекта, просто прямая сериализация объекта (хотя я добавлю их при необходимости).
моя проблема в том, что мне нужно добавить члены данных в объект. Если я это сделаю, я знаю, что старый файл настроек не десериализуется.
есть ли способ указать значения по умолчанию для добавленных элементов или простой путь к игнорируйте их, если они отсутствуют в XML?
7 ответов
Он должен десериализоваться просто отлично, он просто будет использовать конструктор по умолчанию для инициализации элементов. Поэтому они останутся неизменными.
С MSDN
Лучшие Практики чтобы обеспечить правильное поведение управления версиями, следуйте этим правилам при изменении типа от версии к версии:
при добавлении нового сериализованного поля примените атрибут OptionalFieldAttribute атрибут.
при удалении атрибута NonSerializedAttribute из поля (что не был сериализуемым в предыдущей версии), применить Атрибут OptionalFieldAttribute.
для всех необязательных полей задайте значимые значения по умолчанию, используя обратные вызовы сериализации, если по умолчанию не допустимы 0 или null.
Я попытался смоделировать ваш случай, когда в новой версии класса есть новый член с именем Element2. инициализировал мой новый член "это новый член" вот полное доказательство
Тест1 предполагает, что вы сериализованы со старым определением Корневой класс с одним Element1
Условие_2 при сериализации и де сериализации с новым определением корневого класса
ответ ваш вопрос любой способ предоставить значения по умолчанию вы должны использовать "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 и manully определить, как инициализировать значения (если это может быть готово)
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
}
в зависимости от ваших требований, это может быть упрощено в один метод на модели (или загрузчик модели).
Это также может быть использовано для проверки модели после десериализация.
Edit: вы можете сериализовать, используя следующий код
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;
}
}
поэтому, если вы Десериализуете и свойство не заполнено, оно будет использовать значение defaultValue в качестве значения, и вы можете использовать свой старый XML для создания нового объекта.
Если в новой версии были удалены свойства, это должно пройти без каких-либо проблем через XMLSerialization. (насколько мне известно)
вы можете пользователей ExtendedXmlSerializer. Этот сериализатор поддерживает десериализацию старой версии xml. Вот пример десериализовать старую версию xml
вы даже можете читать разные версии объекта из одного файла.
.NET обеспечивает довольно много для сериализации / десериализации и управления версиями.
1) атрибуты пользователя DataContract / DataMember и DataContractSerializer
2) в соответствии с MSDN эти изменения нарушаются
- изменение имени или значения пространства имен контракта данных.
- изменение порядка элементов данных с помощью свойства Order атрибута DataMemberAttribute.
- переименовать элемент данных.
- изменение контракт данных члена данных.
3) Когда тип с дополнительным полем десериализуется в тип с отсутствующим полем, дополнительная информация игнорируется.
4) Когда тип с отсутствующим полем десериализуется в тип с дополнительным полем, дополнительное поле остается со значением по умолчанию, обычно нулевым или нулевым.
5) рассмотрите возможность использования IExtensibleDataObject для управления версиями