我在我的應用程序中使用了一種「會話每會話」模式實現。 在這種方法中,「NHibernate會話」保留在「HTTP會話」中。在每個「Http請求」中,「NHibernate Session」在開始時重新連接,並在請求結束時斷開連接。 「NHibernate會話」在「對話」結束時保存在「HTTP會話」中。這工作得很好。序列化/反序列化「NHibernate會話」,懶化官方化錯誤(「StateServer模式」用於集羣)
問題:
現在我對 「的Http會話」 使用"StateServer mode"考慮。 因此,「HTTP會話」中的所有對象都必須是可序列化的,包括「NHibernate會話」。
因此,我正在做一個概念證明來驗證「NHibernate會話」及其緩存對象的序列化/反序列化的工作原理。
「概念驗證」是以下單元測試。目標是讓它通過。
代碼:
/*069*/ [Test]
/*070*/ public void Test()
/*071*/ {
/*072*/ //for inspecting the SQL commands
/*073*/ NhSqlLogCapture hhSqlLogCapture = new NhSqlLogCapture();
/*074*/
/*075*/ MemoryStream ms = new MemoryStream();
/*076*/
/*077*/ ISession sessionBefore = null;
/*078*/ ISession sessionAfter = null;
/*079*/
/*080*/ //querying before the serialization.
/*081*/ try
/*082*/ {
/*083*/ sessionBefore = this.sessionFactory.OpenSession();
/*084*/ sessionBefore.FlushMode = FlushMode.Auto;
/*085*/
/*086*/ hhSqlLogCapture.Enable();
/*087*/
/*088*/ //Querying only 'DetailEtt'
/*089*/ hhSqlLogCapture.SqlStatments.Clear();
/*090*/ ICriteria crt = sessionBefore.CreateCriteria<DetailEtt>();
/*091*/ crt
/*092*/ .SetFetchMode("Master", FetchMode.Select);
/*093*/ IList<DetailEtt> cen1DetailList = crt.List<DetailEtt>();
/*094*/
/*095*/ //BEGIN: Serializing
/*096*/ //also serializing an instance of 'DetailEtt' to verify that keeps only one instance to the same database record.
/*097*/ sessionBefore.Disconnect();
/*098*/ Object[] serializationArray = new object[] { sessionBefore, sessionBefore.Get<DetailEtt>(1) };
/*099*/ BinaryFormatter bf = new BinaryFormatter();
/*100*/ bf.Serialize(ms, serializationArray);
/*101*/ //END: Serializing
/*102*/
/*103*/ //assertions
/*104*/ //Checking the sql command executed.
/*105*/ Assert.AreEqual(1, hhSqlLogCapture.SqlStatments.Count, "hhSqlLogCapture.SqlStatments.Count");
/*106*/ Regex rx = new Regex("(?is).*SELECT.*FROM.*DETAIL.*");
/*107*/ Assert.IsTrue(rx.IsMatch(hhSqlLogCapture.SqlStatments[0]), hhSqlLogCapture.SqlStatments[0]);
/*108*/
/*109*/ hhSqlLogCapture.Disable();
/*110*/ }
/*111*/ finally
/*112*/ {
/*113*/ sessionBefore = null;
/*114*/ }
/*115*/
/*116*/ try
/*117*/ {
/*118*/ //BEGIN: Deserializing
/*119*/ BinaryFormatter bf = new BinaryFormatter();
/*120*/ ms.Seek(0, SeekOrigin.Begin);
/*121*/ Object[] deserializationArray = (Object[])bf.Deserialize(ms);
/*122*/ DetailEtt dtEttDeserialized = (DetailEtt)deserializationArray[1];
/*123*/ sessionAfter = (ISession)deserializationArray[0];
/*124*/ //BEGIN: Deserializing
/*125*/
/*126*/ IDbConnection conn = this.dbProvider.CreateConnection();
/*127*/ conn.Open();
/*128*/ sessionAfter.Reconnect(conn);
/*129*/
/*130*/ //Enabling again because the session loses the reference to the log (I think).
/*131*/ hhSqlLogCapture.Enable();
/*132*/ hhSqlLogCapture.SqlStatments.Clear();
/*133*/
/*134*/ DetailEtt dtEtdSSGet = sessionAfter.Get<DetailEtt>(1);
/*135*/ MasterEtt mtEtd = dtEtdSSGet.Master;
/*136*/ Console.WriteLine(mtEtd.Description);
/*137*/
/*138*/ //assertions
/*139*/ //Checking the sql command executed.
/*140*/ Assert.AreEqual(1, hhSqlLogCapture.SqlStatments.Count, "hhSqlLogCapture.SqlStatments.Count");
/*141*/ Regex rx = new Regex("(?is).*SELECT.*FROM.*MASTER.*");
/*142*/ Assert.IsTrue(rx.IsMatch(hhSqlLogCapture.SqlStatments[0]), hhSqlLogCapture.SqlStatments[0]);
/*143*/ //verify that keeps only one instance to the same database record
/*144*/ Assert.AreSame(dtEttDeserialized, dtEtdSSGet, "verify that keeps only one instance to the same database record");
/*145*/ }
/*146*/ finally
/*147*/ {
/*148*/ sessionAfter.Close();
/*149*/ }
/*150*/ }
測試通過上幾乎所有的東西。但嘗試加載「懶惰」實體時失敗。
錯誤:
SofPOC.Questions.SerializeSession.SerializeSessionTest.Test:
NHibernate.LazyInitializationException : Initializing[SofPOC.Questions.SerializeSession.Entities.MasterEtt#5]-Could not initialize proxy - no Session. em NHibernate.Proxy.AbstractLazyInitializer.Initialize()
at NHibernate.Proxy.AbstractLazyInitializer.Initialize()
at Spring.Data.NHibernate.Bytecode.LazyInitializer.Invoke(IMethodInvocation invocation) in c:\_prj\spring-net\trunk\src\Spring\Spring.Data.NHibernate21\Data\NHibernate\Bytecode\LazyInitializer.cs:line 101
at Spring.Aop.Framework.AbstractMethodInvocation.Proceed() in c:\_prj\spring-net\trunk\src\Spring\Spring.Aop\Aop\Framework\AbstractMethodInvocation.cs:line 284
at Spring.Aop.Framework.DynamicProxy.AdvisedProxy.Invoke(Object proxy, Object target, Type targetType, MethodInfo targetMethod, MethodInfo proxyMethod, Object[] args, IList interceptors) in c:\_prj\spring-net\trunk\src\Spring\Spring.Aop\Aop\Framework\DynamicProxy\AdvisedProxy.cs:line 208
at DecoratorAopProxy_9872659265c04d36bc9738f2aaddfb08.get_Description()
at SofPOC.Questions.SerializeSession.SerializeSessionTest.Test() in C:\Users\hailtondecastro\lixo\stackoverflow\dotnet\StackoverflowNetPOCs_20120718\src\SofPOC.Net4.NH2.Spring13.2010\Questions\SerializeSession\SerializeSessionTest.cs:line 136
DetailEtt:
[Serializable]
public class DetailEtt
{
private Int32? id;
/// <summary>
/// PK
/// </summary>
public virtual Int32? Id
{
get { return id; }
set { id = value; }
}
private String description;
/// <summary>
/// TODO:
/// </summary>
public virtual String Description
{
get { return description; }
set { description = value; }
}
private MasterEtt master;
/// <summary>
/// TODO:
/// </summary>
public virtual MasterEtt Master
{
get { return master; }
set { master = value; }
}
}
MasterEtt:
[Serializable]
public class MasterEtt
{
private Int32? id;
/// <summary>
/// PK
/// </summary>
public virtual Int32? Id
{
get { return id; }
set { id = value; }
}
private String description;
/// <summary>
/// TODO:
/// </summary>
public virtual String Description
{
get { return description; }
set { description = value; }
}
}
DetailEtt.hbm.xml:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="SofPOC.Questions.SerializeSession.Entities" assembly="SofPOC.Net4.NH2.Spring13">
<class name="DetailEtt" table="DETAIL">
<id name="Id" type="Int32">
<column name="ID" sql-type="INTEGER"></column>
<generator class="assigned"></generator>
</id>
<property name="Description" type="String">
<column name="DESCRIPTION" sql-type="VARCHAR(100)"></column>
</property>
<many-to-one name="Master" fetch="select">
<column name="MS_ID" sql-type="INTEGER"></column>
</many-to-one>
</class>
</hibernate-mapping>
MasterEtt.hbm.xml:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="SofPOC.Questions.SerializeSession.Entities" assembly="SofPOC.Net4.NH2.Spring13">
<class name="MasterEtt" table="MASTER">
<id name="Id" type="Int32">
<column name="ID" sql-type="INTEGER"></column>
<generator class="assigned"></generator>
</id>
<property name="Description" type="String">
<column name="DESCRIPTION" sql-type="VARCHAR(100)"></column>
</property>
</class>
</hibernate-mapping>
問題:
即使重新連接反序列化 「Hibernate的Session」 我得到的 「懶加載錯誤」 之後。如何避免這種「延遲加載錯誤」而不必重新連接實體?
我使用:
- Spring.Net 1.3.2
- NHibernate的2.1.2
- System.Data.SQLite 1.0.80。0
的完整源代碼是在這裏:Q11553780.7z
注:
- 打開解決方案之前運行 「\依賴(」 \ SRC \ SofPOC.2010.sln」)。 \ setup.bat「加載依賴關係。
- 有關依賴性的說明,請參見「。\ readme.txt」和「。\ dependencies \ readme.txt」。
編輯:
我發現這個問題的原因是NHibernate的的NHibernate.Proxy.AbstractLazyInitializer
類。字段_session
被標記爲[NonSerialized]
。這使得這個字段不被序列化。因此在反序列化之後它是空的。
看到代碼:
namespace NHibernate.Proxy
{
[Serializable]
public abstract class AbstractLazyInitializer : ILazyInitializer
{
/// <summary>
/// If this is returned by Invoke then the subclass needs to Invoke the
/// method call against the object that is being proxied.
/// </summary>
protected static readonly object InvokeImplementation = new object();
private object _target = null;
private bool initialized;
private object _id;
[NonSerialized]
private ISessionImplementor _session;
...
EDITED 2:
問題的原因是真正的屬性[非序列化],因爲當我做下面的 「黑客」 測試通過。通過反思,我將「_session」的屬性從「Private | NotSerialized」更改爲僅「Private」。
的黑客:
protected override void OnSetUp()
{
//Hacking "_session"
Type aliType = Type.GetType("NHibernate.Proxy.AbstractLazyInitializer, NHibernate");
FieldInfo fiSession = aliType.GetField("_session", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
FieldInfo fi_m_fieldAttributes =
fiSession.GetType().GetField(
"m_fieldAttributes",
System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Instance);
// changing it from "Private | NotSerialized" to only "Private"
fi_m_fieldAttributes.SetValue(fiSession, FieldAttributes.Private);
base.OnSetUp();
}
感謝您的幫助。我想測試三個選項。我有一個考慮要做:aboult「...即使它沒有標記NonSerializable。」我認爲你在這一點上是錯誤的。使用反射,我在「_session」的FieldInfo中將其從「Private | NotSerialized」更改爲僅「Private」。它的工作。 – Hailton 2012-07-25 21:40:20
第三種解決方案似乎更具吸引力。第一個解決方案似乎有點不切實際,因爲我必須確定「動態代理」的實例,但我同意它的工作原理。第二種解決方案正是我想要避免的,這就是爲什麼我使用「會話每會話」模式。 – Hailton 2012-07-26 00:17:34
現在,我在白天再次閱讀我的答案,「如果反序列化會話,它不會神奇地出現在_session字段中(即使它未標記爲NonSerializable)。」沒有任何意義,所以我從答案中刪除了它。 – Jeroen 2012-07-30 13:51:22