簡體   English   中英

在C#中,如何將XML從較舊的對象反序列化為更新的對象並忽略缺少的xml元素?

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

我有一個自定義設置文件,我使用XmlSerializer序列化/反序列化。 我的對象定義中沒有定義架構且沒有序列化標簽,只是直接對象序列化(盡管如果需要我會添加它們)。

我的問題是我需要向對象添加數據成員。 如果我這樣做,我知道舊的設置文件不會反序列化。

有沒有辦法為添加的成員指定默認值,或者如果XML中缺少這些默認值,可以采用一些簡單的方法來忽略它們?

來自MSDN

最佳實踐要確保正確的版本控制行為,請在從版本到版本修改類型時遵循以下規則:

  • 添加新的序列化字段時,應用OptionalFieldAttribute屬性。

  • 從字段中刪除NonSerializedAttribute屬性(在以前的版本中不可序列化)時,應用OptionalFieldAttribute屬性。

  • 對於所有可選字段,請使用序列化回調設置有意義的默認值,除非可以接受0或null作為默認值。

我試圖模擬你的情況,在新版本的類中有新成員名為Element2。 我的新成員初始化為“這是新成員”這里是完全證明

Test1假設你使用只有一個Element1的Root類的舊定義序列化

使用Root Class的新定義進行序列化和反序列化時的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>");
  }
}

//這是輸出 在此輸入圖像描述

它應該反序列化很好,它將只使用默認構造函數來初始化項目。 所以它們將保持不變。

您需要使用自定義方法手動處理它並使用適當的屬性標記它們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();
        }

如果你遵循這種模式,它是相當簡單的:

  • 通過實現ISerializable自己處理序列化/反序列化
  • 使用此選項可序列化對象的成員序列化版本號。
  • 在反序列化代碼上,針對版本號運行switch-case語句。 當你開始時,你將只有一個版本 - 最初的反序列化代碼。 隨着您的進步,您將在新的序列化快照中標記更新的版本號。
  • 對於對象的未來版本, 始終保持現有的反序列化代碼不變,或者將其修改為映射到重命名/重構的成員,並且主要只是為新的序列化版本添加新的case語句。

這樣,即使序列化快照是從以前版本的程序集生成的,您也可以成功反序列化以前的數據。

使用[System.ComponentModel.DefaultValueAttribute]為Serialization定義DefaultValues。

來自MSDN的示例:

private bool myVal=false;

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

因此,如果您使用DeSerialize並且未填充該屬性,則它將使用defaultValue作為Value,您可以使用舊的XML生成新的Object。

如果在新版本中刪除了屬性,則應該通過XMLSerialization沒有任何問題。 (我所知道的)

您可以使用ExtendedXmlSerializer 此序列化程序支持反序列化舊版本的xml。 以下是反序列化舊版xml的示例

您甚至可以從一個文件中讀取對象的不同版本。

.NET為序列化/反序列化和版本控制提供了很多功能。

1)用戶DataContract / DataMember屬性和DataContractSerializer

2)根據MSDN這些變化正在打破

  • 更改數據協定的名稱或命名空間值。
  • 使用DataMemberAttribute的Order屬性更改數據成員的順序。
  • 重命名數據成員。
  • 更改數據成員的數據協定。

3)當具有額外字段的類型被反序列化為具有缺少字段的類型時,將忽略額外信息。

4)當具有缺失字段的類型被反序列化為具有額外字段的類型時,額外字段保留其默認值,通常為零或null。

5)考慮使用IExtensibleDataObject進行版本控制

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM