Note that there are some explanatory texts on larger screens.

plurals
  1. POSerialize/deserialize "NHibernate Session", lazy inicialization error ("StateServer mode" for clustering)
    text
    copied!<p>I use in my application a kind of "session-per-conversation" pattern implementation. In this approach the "NHibernate Session" is kept on the "HTTP Session". In every "Http Request" the "NHibernate Session" is reconnected at the beginning and disconnected at the end of the request. The "NHibernate Session" is kept in "HTTP Session" by the end of the "conversation". This is working very well.</p> <p><strong>The Problem:</strong></p> <p>Now I'm considering using <a href="http://msdn.microsoft.com/en-us/library/ms178586.aspx" rel="nofollow">"StateServer mode"</a> on the "Http Session". Thus, all objects in the "HTTP Session" must be serializable, including the "NHibernate Session".</p> <p>So I'm doing a proof of concept to validate that the serialization/deserialization of a "NHibernate Session" and its cached objects works.</p> <p>The "proof of concept" is the following unit test. And the goal is to make it pass.</p> <p>Code: </p> <pre><code>/*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&lt;DetailEtt&gt;(); /*091*/ crt /*092*/ .SetFetchMode("Master", FetchMode.Select); /*093*/ IList&lt;DetailEtt&gt; cen1DetailList = crt.List&lt;DetailEtt&gt;(); /*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&lt;DetailEtt&gt;(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&lt;DetailEtt&gt;(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*/ } </code></pre> <p>The test passes on almost everything. But it fails when tries to load an entity that is "Lazy". </p> <p>The error:</p> <pre><code>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 </code></pre> <p>DetailEtt: </p> <pre><code>[Serializable] public class DetailEtt { private Int32? id; /// &lt;summary&gt; /// PK /// &lt;/summary&gt; public virtual Int32? Id { get { return id; } set { id = value; } } private String description; /// &lt;summary&gt; /// TODO: /// &lt;/summary&gt; public virtual String Description { get { return description; } set { description = value; } } private MasterEtt master; /// &lt;summary&gt; /// TODO: /// &lt;/summary&gt; public virtual MasterEtt Master { get { return master; } set { master = value; } } } </code></pre> <p>MasterEtt: </p> <pre><code>[Serializable] public class MasterEtt { private Int32? id; /// &lt;summary&gt; /// PK /// &lt;/summary&gt; public virtual Int32? Id { get { return id; } set { id = value; } } private String description; /// &lt;summary&gt; /// TODO: /// &lt;/summary&gt; public virtual String Description { get { return description; } set { description = value; } } } </code></pre> <p>DetailEtt.hbm.xml: </p> <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="SofPOC.Questions.SerializeSession.Entities" assembly="SofPOC.Net4.NH2.Spring13"&gt; &lt;class name="DetailEtt" table="DETAIL"&gt; &lt;id name="Id" type="Int32"&gt; &lt;column name="ID" sql-type="INTEGER"&gt;&lt;/column&gt; &lt;generator class="assigned"&gt;&lt;/generator&gt; &lt;/id&gt; &lt;property name="Description" type="String"&gt; &lt;column name="DESCRIPTION" sql-type="VARCHAR( 100 )"&gt;&lt;/column&gt; &lt;/property&gt; &lt;many-to-one name="Master" fetch="select"&gt; &lt;column name="MS_ID" sql-type="INTEGER"&gt;&lt;/column&gt; &lt;/many-to-one&gt; &lt;/class&gt; &lt;/hibernate-mapping&gt; </code></pre> <p>MasterEtt.hbm.xml: </p> <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="SofPOC.Questions.SerializeSession.Entities" assembly="SofPOC.Net4.NH2.Spring13"&gt; &lt;class name="MasterEtt" table="MASTER"&gt; &lt;id name="Id" type="Int32"&gt; &lt;column name="ID" sql-type="INTEGER"&gt;&lt;/column&gt; &lt;generator class="assigned"&gt;&lt;/generator&gt; &lt;/id&gt; &lt;property name="Description" type="String"&gt; &lt;column name="DESCRIPTION" sql-type="VARCHAR( 100 )"&gt;&lt;/column&gt; &lt;/property&gt; &lt;/class&gt; &lt;/hibernate-mapping&gt; </code></pre> <p><strong>The Question:</strong></p> <p>Even after reconnecting the deserialized "Hibernate Session" I get "Lazy Load Error". How to avoid this kind of "Lazy Load Error" without having to reattach the entities?</p> <p>I'm using:</p> <ul> <li>Spring.Net 1.3.2</li> <li>NHibernate 2.1.2</li> <li>System.Data.SQLite 1.0.80.0</li> </ul> <p>The complete source is here: <a href="https://dl.dropbox.com/u/49510149/stackoverflow/dotnet/Q11553780.7z" rel="nofollow">Q11553780.7z</a></p> <p><strong>NOTES:</strong> </p> <ul> <li>Before opening the solution (".\ Src\SofPOC.2010.sln") run ".\Dependencies\setup.bat" to load dependencies.</li> <li>See ".\readme.txt" and ".\dependencies\readme.txt" for instructions about the dependencies.</li> </ul> <p><strong>EDITED:</strong></p> <p>I found that the cause of the problem is in the class <code>NHibernate.Proxy.AbstractLazyInitializer</code> of NHibernate. The field <code>_session</code> is marked as <code>[NonSerialized]</code>. This makes this field not be serialized. Consequently it is null after deserialization.</p> <p>See the code:</p> <pre><code>namespace NHibernate.Proxy { [Serializable] public abstract class AbstractLazyInitializer : ILazyInitializer { /// &lt;summary&gt; /// If this is returned by Invoke then the subclass needs to Invoke the /// method call against the object that is being proxied. /// &lt;/summary&gt; protected static readonly object InvokeImplementation = new object(); private object _target = null; private bool initialized; private object _id; [NonSerialized] private ISessionImplementor _session; ... </code></pre> <p><strong>EDITED 2:</strong></p> <p>The cause of the problem is really the attribute [NonSerialized] because when I make the following "hack" the test passes. By Reflection, I make a change on the attributes of the "_session" from "Private | NotSerialized" to only "Private".</p> <p>The hack:</p> <pre><code> 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(); } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload