使用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