简体   繁体   English

在C#中,如何将XML从较旧的对象反序列化为更新的对象并忽略缺少的xml元素?

[英]In C# how do I deserialize XML from an older object into the updated object and ignore missing xml elements?

What I have is a custom settings file that I serialize/deserialize using an XmlSerializer . 我有一个自定义设置文件,我使用XmlSerializer序列化/反序列化。 I have no schema defined and no serialization tags in my object definition, just straight object serialization (although I will add them if needed). 我的对象定义中没有定义架构且没有序列化标签,只是直接对象序列化(尽管如果需要我会添加它们)。

My issue is that I need to add data members to the object. 我的问题是我需要向对象添加数据成员。 If I do that I know that the old settings file will not deserialize. 如果我这样做,我知道旧的设置文件不会反序列化。

Is there a way to specify default values for the added members or some simple way to ignore them if they are missing from the XML? 有没有办法为添加的成员指定默认值,或者如果XML中缺少这些默认值,可以采用一些简单的方法来忽略它们?

From MSDN 来自MSDN

Best Practices To ensure proper versioning behavior, follow these rules when modifying a type from version to version: 最佳实践要确保正确的版本控制行为,请在从版本到版本修改类型时遵循以下规则:

  • When adding a new serialized field, apply the OptionalFieldAttribute attribute. 添加新的序列化字段时,应用OptionalFieldAttribute属性。

  • When removing a NonSerializedAttribute attribute from a field (that was not serializable in a previous version), apply the OptionalFieldAttribute attribute. 从字段中删除NonSerializedAttribute属性(在以前的版本中不可序列化)时,应用OptionalFieldAttribute属性。

  • For all optional fields, set meaningful defaults using the serialization callbacks unless 0 or null as defaults are acceptable. 对于所有可选字段,请使用序列化回调设置有意义的默认值,除非可以接受0或null作为默认值。

I have tried to simulate your case where in new version of class have new member named Element2. 我试图模拟你的情况,在新版本的类中有新成员名为Element2。 initialized my new member to "This is new member" here is full proof 我的新成员初始化为“这是新成员”这里是完全证明

Test1 assumes you serialized with old definition of Root class with Just one Element1 Test1假设你使用只有一个Element1的Root类的旧定义序列化

Test2 when you serialized and de serialized with new definition of Root Class 使用Root Class的新定义进行序列化和反序列化时的Test2

To answer your question any way to provide default values you should use "OptionalField" 要以任何方式回答您的问题以提供默认值,您应该使用“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>");
  }
}

// here is the output //这是输出 在此输入图像描述

It should deserialize just fine, it will just use the default constructor to initialize the items. 它应该反序列化很好,它将只使用默认构造函数来初始化项目。 So they will remain unchanged. 所以它们将保持不变。

You need to manually handle it using custom methods and marking them with the appropriate attributes OnSerializing/OnSerialized/OnDeserializing/OnDeserialized and manully determine how to initialize the values (if it can be done) 您需要使用自定义方法手动处理它并使用适当的属性标记它们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 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

a better way is to determine the version before hand and use a strategy pattern to do the deserialization. 更好的方法是预先确定版本并使用策略模式进行反序列化。 This isn't always possible so use what I suggest in that case. 这并不总是可行的,所以请使用我在这种情况下的建议。

Update : The previous answer is only applicable to binary serialization. 更新 :上一个答案仅适用于二进制序列化。 For regular Xml you can use this method. 对于常规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

    }

Depending on your requirements, this could be simplified into a single method on the model (or a model loader). 根据您的要求,可以将其简化为模型(或模型加载器)上的单个方法。

This could also be used for model validation after deserialization. 这也可以在反序列化后用于模型验证。

Edit: you can serialize using the following code 编辑:您可以使用以下代码进行序列化

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

            f.Close();
        }

If you follow this pattern it's fairly straightforward: 如果你遵循这种模式,它是相当简单的:

  • Handle the serialization/deserialization yourself by implementing ISerializable 通过实现ISerializable自己处理序列化/反序列化
  • Use this to serialize both the members of your object and a serialization version number. 使用此选项可序列化对象的成员序列化版本号。
  • On the deserialization code, run a switch-case statement against the version number. 在反序列化代码上,针对版本号运行switch-case语句。 When you start out, you'll just have one version - the initial deserialization code. 当你开始时,你将只有一个版本 - 最初的反序列化代码。 As you go forward, you'll stamp a newer version number into newly serialized snapshots. 随着您的进步,您将在新的序列化快照中标记更新的版本号。
  • For future versions of your object, always leave the existing deserialization code intact, or modify it to map to members you rename / refactor, and primarily just add a new case statement for the new serialization version. 对于对象的未来版本, 始终保持现有的反序列化代码不变,或者将其修改为映射到重命名/重构的成员,并且主要只是为新的序列化版本添加新的case语句。

In this way, you will be able to successfully deserialize previous data even if the serialization snapshot was generated from a previous version of your assembly. 这样,即使序列化快照是从以前版本的程序集生成的,您也可以成功反序列化以前的数据。

Use the [System.ComponentModel.DefaultValueAttribute] to define DefaultValues for Serialization. 使用[System.ComponentModel.DefaultValueAttribute]为Serialization定义DefaultValues。

Example from the MSDN : 来自MSDN的示例:

private bool myVal=false;

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

So if you DeSerialize and the Property is not filled it would use the defaultValue as Value and you can use your old XML to generate the new Object. 因此,如果您使用DeSerialize并且未填充该属性,则它将使用defaultValue作为Value,您可以使用旧的XML生成新的Object。

If there were Properties removed in the new Version this should go without any problems trough XMLSerialization. 如果在新版本中删除了属性,则应该通过XMLSerialization没有任何问题。 (as far as i know) (我所知道的)

You can user ExtendedXmlSerializer . 您可以使用ExtendedXmlSerializer This serializer support deserialization old version of xml. 此序列化程序支持反序列化旧版本的xml。 Here is an example of deserialize old version of xml 以下是反序列化旧版xml的示例

You can even read different versions of an object from one file. 您甚至可以从一个文件中读取对象的不同版本。

.NET provide quite a lot for serializing/deserializing and versioning. .NET为序列化/反序列化和版本控制提供了很多功能。

1) user DataContract / DataMember attributes and DataContractSerializer 1)用户DataContract / DataMember属性和DataContractSerializer

2) according to MSDN these changes are breaking 2)根据MSDN这些变化正在打破

  • Changing the Name or Namespace value of a data contract. 更改数据协定的名称或命名空间值。
  • Changing the order of data members by using the Order property of the DataMemberAttribute. 使用DataMemberAttribute的Order属性更改数据成员的顺序。
  • Renaming a data member. 重命名数据成员。
  • Changing the data contract of a data member. 更改数据成员的数据协定。

3) When a type with an extra field is deserialized into a type with a missing field, the extra information is ignored. 3)当具有额外字段的类型被反序列化为具有缺少字段的类型时,将忽略额外信息。

4) When a type with a missing field is deserialized into a type with an extra field, the extra field is left at its default value, usually zero or null. 4)当具有缺失字段的类型被反序列化为具有额外字段的类型时,额外字段保留其默认值,通常为零或null。

5) Consider using IExtensibleDataObject for versioning 5)考虑使用IExtensibleDataObject进行版本控制

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM