具備事件(Events)的物件在進行序列化(Serialization)過程中的問題
ORM的程式設計觀念在現代已經是大家普遍使用的技巧了,但是用來塑造ORM的類別,往往我們會賦予給他更多的非資料使用方式,例如可能會有一堆方法,甚至是事件的觸發。ClassType複雜歸複雜,但是總是運作的良好得當。BUT,人生最重要的就是這個BUT,當我們可能需要把這個類別狀態保留時(Application、Session、ViewState...或其他提供序列化Cache服務的機制),就需要序列化它,而事件若有被外部調用掛載,這時候你就會遭遇到莫名其妙的問題了。
這個問題的中英文描述如下,大致上就是在跟你講某一個類別物件不可序列化:
無法序列化工作階段狀態。在 'StateServer' 和 'SQLServer' 模式中,ASP.NET 將序列化工作階段狀態物件,因此不允許無法序列化的物件或 MarshalByRef 物件。在 'Custom' 模式中,自訂工作階段狀態存放區執行類似的序列化作業時,也會有同樣的限制。
Unable to serialize the session state. Please note that non-serializable objects or MarshalByRef objects are not permitted when session state mode is 'StateServer' or 'SQLServer'.
其實這個問題有很多種解決方法,最常見的大概就是程式設計正規化魔人跟你說的,ORM物件就是ORM物件,你不要汙染它好嗎?請重構Brabra...說的是沒錯啦,但每個人有每個人的人生觀,畢竟這個系統現在是能動的沒錯吧?程式碼大致上也呈現了結構化而不是髒亂毫無章法沒錯吧?你要把人生浪費在Coding,我要把人生浪費在我覺得更美好的事物上,誰對誰錯?
先來看出錯的程式碼:
ORM_EventClass.cs
namespace EventTest
{
public delegate void EventHandler(object sender, EventArgs e);
[Serializable]
public class ORM_EventClass
{
public string cName { get; set; }
public string cPhone { get; set; }
public event EventHandler OnWriteFinish;
public void WriteToDatabase()
{
//BraBra... Do data write ...
if (OnWriteFinish != null) { OnWriteFinish(this, new EventArgs()); }
}
}
}
Test.aspx(錯誤會發生在Session回存調用時,理由是OnWriteFinish事件掛載方法無法序列化)
<script runat="server">
public void Page_Load(object sender, EventArgs e)
{
EventTest.ORM_EventClass oTemp = new EventTest.ORM_EventClass();
oTemp.cName = "王小明";
oTemp.cPhone = "0912345678";
oTemp.OnWriteFinish += ShowSuccess; //Can NOT serializable
oTemp.WriteToDatabase();
Session["JustTest"] = oTemp;
}
public void ShowSuccess(Object sender, EventArgs e)
{
uMsg.Text = "寫入成功。";
}
</script>
解決方案A
重構的那件事情就不要再提了,誰有那種美國時間改牽一髮動全身的Code啊!答案就是在寫入Session前,把事件綁定卸除。蠢!但很有效!
<script runat="server">
public void Page_Load(object sender, EventArgs e)
{
EventTest.ORM_EventClass oTemp = new EventTest.ORM_EventClass();
oTemp.cName = "王小明";
oTemp.cPhone = "0912345678";
oTemp.OnWriteFinish += ShowSuccess; //Can NOT serializable
oTemp.WriteToDatabase();
//回存Session前先卸除
oTemp.OnWriteFinish -= ShowSuccess; //Stupid! But it works.
Session["JustTest"] = oTemp;
}
public void ShowSuccess(Object sender, EventArgs e)
{
uMsg.Text = "寫入成功。";
}
</script>
解決方案B
另外有一個解決方案就是對Class動刀,就是使用[NonSerialized]屬性來叫.NET不要幫你做事件掛載點這部分的序列化工作,但是這邊有雷,當我們把這個屬性(Attributes)鍵入下去的時候,會中另外一個雷,程式碼如下:
namespace EventTest
{
public delegate void EventHandler(object sender, EventArgs e);
[Serializable]
public class ORM_EventClass
{
public string cName { get; set; }
public string cPhone { get; set; }
[NonSerialized]
public event EventHandler OnWriteFinish;
public void WriteToDatabase()
{
//BraBra... Do data write ...
if (OnWriteFinish != null) { OnWriteFinish(this, new EventArgs()); }
}
}
}
編譯器會跟你講無法編譯,文字資訊如下:
屬性 'NonSerialized' 在此宣告類型上無效。它只在 'field' 宣告上有效。
Attribute 'NonSerialized' is not valid on this declaration type. It is only valid on 'field' declarations.
這部分要正式的解法其實很曲折,不符合本人簡單解決事情的習慣,因此在這邊教大家一個小撇步來解決:
[field: NonSerialized]
public event EventHandler OnWriteFinish;
Yes,收工!繼續快樂過生活。
請注意,當你採用解決方案B時,就不需要再使用解決方案A:將事件移除的方法喔。