如何在重命名某些属性后反序列化旧 XML 文件?

How to deserialize old XML files after some properties have been renamed?

为简单起见,假设我有这个 class:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

我使用以下两种方法 serialization/deserialization:

public void Serialize(Person person, string outputFilePath)
{
    using (var stream = 
           new FileStream(outputFilePath, FileMode.Create, FileAccess.Write))
    {
        var serializer = new XmlSerializer(person.GetType());
        serializer.Serialize(stream, person);
    }
}

public Person Deserialize(string filePath)
{
    using (var stream = new FileStream(filePath, FileMode.Open))
    {
        var serializer = new XmlSerializer(typeof(Person));
        return (Person)serializer.Deserialize(stream);
    }
}

现在,假设我将一些 Person 对象序列化为 XML 文件,然后重命名 Person class 中的一个或多个 属性:

// Renamed from `Name`
public string FullName { get; set; }

我的目标是允许程序仍然反序列化那些具有 Name 元素的旧 XML 文件,而不是 FullName

XmlElementAttribute 在这里无济于事:

// If I do this, I can't deserialize from files created after
// the property has been renamed.
[XmlElement("Name")]
public string FullName { get; set; }

如果我可以支持多个旧名称,也许使用一些属性,那将是理想的。例如,

[XmlAlternateDeserializationElement("Name")]
[XmlAlternateDeserializationElement("Label")]
public string FullName { get; set; }

但任何其他方式就足够了。我怎样才能做到这一点?

下面显示了如何在一个或多个 属性 名称发生更改时反序列化一个 XML 文件名。

注意:在下面的代码中,当XML被序列化时,它将以较新的格式保存。

XML - 原创

<Person>
    <Id>1</Id>
    <Name>John Smith</Name>
    <City>Some City</City>
</Person>

XmlPersonOriginal:

using System.Xml.Serialization;

namespace XmlSerializationRenamedProperty
{
    [XmlRoot(ElementName = "Person", IsNullable = false)]
    public class XmlPersonOriginal
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string City { get; set; }
        public string StateOrProvince { get; set; }
    }
}

序列化

public static void SerializeObjectToXMLFile(object obj, string xmlFilename)
{
    using (System.IO.TextWriter xmlStream = new System.IO.StreamWriter(xmlFilename))
    {
        System.Xml.Serialization.XmlSerializerNamespaces ns = new System.Xml.Serialization.XmlSerializerNamespaces();
        ns.Add(string.Empty, "urn:none");

        //create new instance
        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());

        //write to XML file
        serializer.Serialize(xmlStream, obj, ns);
    }
}

用法(序列化):

string filename = @"C:\Temp\Person.xml";
XmlPersonOriginal person = new XmlPersonOriginal() { Id = 1, Name = "John Smith", City = "Some City"};
Helper.SerializeObjectToXMLFile(person, filename);

反序列化

public static T DeserializeXMLFileToObject<T>(string xmlFilename)
{
    T rObject = default(T);

    using (System.IO.StreamReader xmlStream = new System.IO.StreamReader(xmlFilename))
    {
        System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
        rObject = (T)serializer.Deserialize(xmlStream);
    }

    return rObject;
}

用法(反序列化):

string filename = @"C:\Temp\Person.xml";
XmlPersonOriginal person = Helper.DeserializeXMLFileToObject<XmlPersonOriginal>(filename);

现在,如果决定将 属性 名称从 Name 更改为 FullName,则可以创建一个新的 class (XmlPerson_v2)继承自原始 (XmlPersonOriginal) 并覆盖任何已更改的 属性 名称。为了能够覆盖 属性,将关键字“virtual”添加到 属性.

XmlPersonOriginal:

using System.Xml.Serialization;

namespace XmlSerializationRenamedProperty
{
    [XmlRoot(ElementName = "Person", IsNullable = false)]
    public class XmlPersonOriginal
    {
        public int Id { get; set; }
        public virtual string Name { get; set; }
        public string City { get; set; }
        public string StateOrProvince { get; set; }
    }
}

XmlPerson_v2:

using System;
using System.Xml.Serialization;

namespace XmlSerializationRenamedProperty
{
    [XmlRoot(ElementName = "Person", IsNullable = false)]
    public class XmlPerson_v2 : XmlPersonOriginal
    {
        public string FullName { get; set; }

        #pragma warning disable CS0809
        [XmlIgnore]
        [Obsolete("Name is deprecated, use FullName instead.")]
        public override string Name 
        {
            get
            {
                return base.Name;
            }

            set
            {
                FullName = value;
            }
        }
    }
}

用法(序列化):

string filename = @"C:\Temp\PersonModified.xml";
XmlPerson_v2 person = new XmlPerson_v2() { Id = 1, FullName = "John Smith", City = "Some City"};
Helper.SerializeObjectToXMLFile(person, filename);

用法(反序列化):

string filename = @"C:\Temp\Person.xml";
XmlPerson_v2 person = Helper.DeserializeXMLFileToObject<XmlPerson_v2>(filename);

用法(反序列化):

string filename = @"C:\Temp\PersonModified.xml";
XmlPerson_v2 person = Helper.DeserializeXMLFileToObject<XmlPerson_v2>(filename);

资源

您可以利用 XmlSerializerUnknownElement / UnknownAttribute 事件来处理旧的元素名称或属性名称:

var serializer = new XmlSerializer(typeof(Person));
serializer.UnknownElement += (sender, e) =>
{
   var person = (Person)e.ObjectBeingDeserialized;
   if (e.Element.Name == "Name")
   {
       person.FullName = e.Element.InnerText;
   }
};

这可以保持您的实际数据 类 干净并将兼容性代码集中在序列化方法中。