1. 程式人生 > >使用XmlSerializer類將物件序列化為Xml格式儲存_支援泛型的Dictionary

使用XmlSerializer類將物件序列化為Xml格式儲存_支援泛型的Dictionary

在一個特殊應用中, 我們需要將記憶體中的一個物件持久化, 而這個物件是來自一個模板類例項化出來的, 不能儲存到資料庫中, 資料庫中只存有此物件的模板.

由於使用到泛型的Dictionary, 而XmlSerializer卻不支援預設的泛型的Dictionary, 為此我找了些資料, 並在此文中以三種不同的方式實現. 本文中約定:

方案1: 不序列化泛型的Dictionary

方案2: 定義支援泛型的Dictionary

薪酬模板PayTemplate,  它通過Dictionary<int, PayItemTemplate>維持著多個薪酬欄目模板PayItemTemplate. 

而一個PayItemTemplate通過Dictionary<int, PayItemTemplate>記錄著那些使用過它的PayItemTemplate.

所以, PayTemplate與PayItemTemplate是一對多關聯; PayItemTemplate是一對多的自關聯.

方案1,2,3中的類關聯都相似, 這裡就不重複了. 詳細差別可在本文末尾下載原始碼檢視.



2. 方案1: 不序列化泛型的Dictionary
既然知道了泛型的Dictionary不被XmlSerializer支援, 我們就避免泛型的Dictionary被序列化, 只需要在欄位上加上XmlIgnore屬性即可.程式碼如下:

   46         [XmlIgnore] //帶有XmlIgnore, 表示序列化時不序列化此屬性

   47         public Dictionary<int, PayItemTemplate> PayItemTemplateDic

   48         {

   49             get { return payItemTemplates; }

   50             set { payItemTemplates = value; }

   51         }

好了,  既然讓泛型的Dictionary不被序列化了, 而我們的需求中又需要將泛型的Dictionary中的物件序列化到Xml中儲存, 那怎麼辦呢?這裡的辦法就是加多一個額外的PayItemTemplate[]陣列欄位, 程式碼實現如下:

   53         ///<summary>

   54         /// 用於序列化PayItemTemplate集合

   55         ///</summary>

   56         public PayItemTemplate[] PayItemTemplates

   57         {

   58             get

   59             {

   60                 List<PayItemTemplate> list = new List<PayItemTemplate>(payItemTemplates.Count);

   61                 foreach (KeyValuePair<int,PayItemTemplate> pit in payItemTemplates)

   62                 {

   63                     list.Add(pit.Value);

   64                 }

   65 

   66                 return list.ToArray();

   67             }

   68             set

   69             {

   70                 payItemTemplates = new Dictionary<int, PayItemTemplate>();

   71                 foreach (PayItemTemplate pit in value)

   72                 {

   73                     payItemTemplates.Add(pit.Id, pit);

   74                 }

   75             }

   76         }

這就是我們需要做的, 下面進行測試, 具體的單元測試, 可在下載原始碼中檢視.
測試結果:
正向序列化:

<?xmlversion="1.0"encoding="utf-8"?>

<PayTemplatexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <Id>100</Id>

  <Name>薪酬模版Test</Name>

  <StartDate>2007-08-16T19:10:43.640625+08:00</StartDate>

  <PayItemTemplates>

    <PayItemTemplate>

      <numbericalCategory>Calculational</numbericalCategory>

      <NumbericalCategory>Calculational</NumbericalCategory>

      <Id>10000</Id>

      <Name>薪酬模板欄目1</Name>

      <PayItemTemplateRelieds>

        <PayItemTemplate>

          <numbericalCategory>Calculational</numbericalCategory>

          <NumbericalCategory>Calculational</NumbericalCategory>

          <Id>20000</Id>

          <Name>薪酬模板欄目2</Name>

          <PayItemTemplateRelieds />

          <Enabled>true</Enabled>

          <Expression>Expression2</Expression>

        </PayItemTemplate>

      </PayItemTemplateRelieds>

      <Enabled>true</Enabled>

      <Expression>Expression</Expression>

    </PayItemTemplate>

    <PayItemTemplate>

      <numbericalCategory>Calculational</numbericalCategory>

      <NumbericalCategory>Calculational</NumbericalCategory>

      <Id>20000</Id>

      <Name>薪酬模板欄目2</Name>

      <PayItemTemplateRelieds />

      <Enabled>true</Enabled>

      <Expression>Expression2</Expression>

    </PayItemTemplate>

  </PayItemTemplates>

  <WorkFlowCategory>Simple</WorkFlowCategory>

</PayTemplate>

反向序列化:

方案1優缺點: 不用定義額外的物件, 但出現多餘的欄位(多餘是因為此欄位只是為了序列化而出現的).


3. 方案2: 定義支援泛型的Dictionary

是否能讓泛型的Dictionary能夠被正常序列化呢? 是, 重新定義一個泛型的SerializableDictionary, 讓它繼承.Net 中的泛型Dictionary並實現IXmlSerializable介面, 本文中的實現參考自XML Serializable Generic Dictionary . IXmlSerializable介面中的兩個關鍵方法:

   25         ///<summary>

   26         /// 反序列化

   27         ///</summary>

   28         ///<param name="reader"></param>

   29         public void ReadXml(XmlReader reader)

   30         {

   31             XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));

   32             XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

   33             if (reader.IsEmptyElement || !reader.Read())

   34             {

   35                 return;

   36             }

   37 

   38             while (reader.NodeType != XmlNodeType.EndElement)

   39             {

   40                 reader.ReadStartElement("item");

   41 

   42                 reader.ReadStartElement("key");

   43                 TKey key = (TKey)keySerializer.Deserialize(reader);

   44                 reader.ReadEndElement();

   45 

   46                 reader.ReadStartElement("value");

   47                 TValue value = (TValue)valueSerializer.Deserialize(reader);

   48                 reader.ReadEndElement();

   49 

   50                 reader.ReadEndElement();

   51                 reader.MoveToContent();

   52 

   53                 this.Add(key, value);

   54             }

   55             reader.ReadEndElement();

   56         }

   57 

   58         ///<summary>

   59         /// 序列化

   60         ///</summary>

   61         ///<param name="writer"></param>

   62         public void WriteXml(XmlWriter writer)

   63         {

   64             XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));

   65             XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

   66 

   67             foreach (TKey key in this.Keys)

   68             {

   69                 writer.WriteStartElement("item");

   70 

   71                 writer.WriteStartElement("key");

   72                 keySerializer.Serialize(writer, key);

   73                 writer.WriteEndElement();

   74 

   75                 writer.WriteStartElement("value");

   76                 valueSerializer.Serialize(writer, this[key]);

   77                 writer.WriteEndElement();

   78 

   79                 writer.WriteEndElement();

   80             }

   81         }



完整程式碼請到原始檔中檢視.

測試結果:
正向序列化:

<?xmlversion="1.0"encoding="utf-8"?>

<PayTemplateV2xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <Id>100</Id>

  <Name>薪酬模版Test</Name>

  <StartDate>2007-08-16T19:08:45.0625+08:00</StartDate>

  <PayItemTemplates>

    <item>

      <key>

        <int>10000</int>

      </key>

      <value>

        <PayItemTemplateV2>

          <numbericalCategory>Calculational</numbericalCategory>

          <NumbericalCategory>Calculational</NumbericalCategory>

          <Id>10000</Id>

          <Name>薪酬模板欄目1</Name>

          <PayItemTemplateReliedList>

            <item>

              <key>

                <int>20000</int>

              </key>

              <value>

                <PayItemTemplateV2>

                  <numbericalCategory>Calculational</numbericalCategory>

                  <NumbericalCategory>Calculational</NumbericalCategory>

                  <Id>20000</Id>

                  <Name>薪酬模板欄目2</Name>

                  <PayItemTemplateReliedList />

                  <Enabled>true</Enabled>

                  <Expression>Expression2</Expression>

                </PayItemTemplateV2>

              </value>

            </item>

          </PayItemTemplateReliedList>

          <Enabled>true</Enabled>

          <Expression>Expression</Expression>

        </PayItemTemplateV2>

      </value>

    </item>

    <item>

      <key>

        <int>20000</int>

      </key>

      <value>

        <PayItemTemplateV2>

          <numbericalCategory>Calculational</numbericalCategory>

          <NumbericalCategory>Calculational</NumbericalCategory>

          <Id>20000</Id>

          <Name>薪酬模板欄目2</Name>

          <PayItemTemplateReliedList />

          <Enabled>true</Enabled>

          <Expression>Expression2</Expression>

        </PayItemTemplateV2>

      </value>

    </item>

  </PayItemTemplates>

  <WorkFlowCategory>Simple</WorkFlowCategory>

</PayTemplateV2>



反向序列化:



方案2中我們定義了SerializableDictionary並實現了IXmlSerializable介面, 惟一的不足就是需要將原始碼中的所有需要序列化的泛型Dictionary改為SerializableDictionary. 好在有VS2005開發工具幫忙, 只需要點選查詢-全部替換就行了; 並且SerializableDictionary具有可重用性.

4. 方案3: 讓每個類實現IXmlSerializable介面

此方案類似於方案2, 只是沒有重新定義新的Dictionary, 而是讓每個帶有泛型Dictionary且需要Xml序列化的類實現IXmlSerializable介面, 自行定義Xml序列化邏輯. 實現程式碼與方案2中的類似. 這裡就省略了, 具體可下載原始碼檢視.

測試結果:
正向序列化:

<?xmlversion="1.0"encoding="utf-8"?>

<PayTemplateV3>

  <Id>100</Id>

  <Name>薪酬模版Test</Name>

  <StartDate>2007年8月17日</StartDate>

  <WorkFlowCategory>Simple</WorkFlowCategory>

  <PayItemTemplates>

    <PayItemTemplateV3>

      <Id>10000</Id>

      <Name>薪酬模板欄目1</Name>

      <Expression>Expression</Expression>

      <Enabled>True</Enabled>

      <NumbericalCategory>Calculational</NumbericalCategory>

      <PayItemTemplateReliedList>

        <PayItemTemplateV3>

          <Id>20000</Id>

          <Name>薪酬模板欄目2</Name>

          <Expression>Expression2</Expression>

          <Enabled>True</Enabled>

          <NumbericalCategory>Calculational</NumbericalCategory>

          <PayItemTemplateReliedList />

        </PayItemTemplateV3>

      </PayItemTemplateReliedList>

    </PayItemTemplateV3>

    <PayItemTemplateV3>

      <Id>20000</Id>

      <Name>薪酬模板欄目2</Name>

      <Expression>Expression2</Expression>

      <Enabled>True</Enabled>

      <NumbericalCategory>Calculational</NumbericalCategory>

      <PayItemTemplateReliedList />

    </PayItemTemplateV3>

  </PayItemTemplates>

</PayTemplateV3>



反向序列化:



為什麼我還要使用方案3呢? 因為在序列化時有可能會出現迴圈引用的情況, 一旦出現了, 我們就只能使用方案3了. 又或者在序列化時, 有些類是需要進行特殊處理的, 我們都可以採用此方法來實現. 方案3顯然是靈活性最高的, 但程式碼工作量較多.


5. 總結

使用.Net 2.0中的XmlSerializer類, 可以方便地將物件轉換成Xml格式, 本文介紹簡單一個序列化應用需求, 而XmlSerializer還有許多更高階的功能, 如序列化屬性設定等.
XmlSerializer並不直接支援泛型Dictionary(Dictionary在XML序列化時遇到的問題及應對方案), 所有我在方案1中, 公開了一個數組屬性, 只是用於序列化時使用; 而方案2中, 重新定義了一個支援Xml序列化的泛型Dictionary, 參考自XML Serializable Generic Dictionary ; 方案3是最靈活但工作最多的一種方案, 為每個需要Xml序列化的類實現IXmlSerializable介面, 然後自定義如何序列化, 過程可參考方案2.

完.

 :) 希望這對你有幫助.

本文中所使用到的原始檔:
Serialize.rar