I use a kind of session-for-talk template implementation in my application. In this approach, the "NHibernate session" is stored in the "HTTP session". In each Http Request, the NHibernate Session reconnects at the beginning and disconnects at the end of the request. The NHibernate session is stored in the HTTP session at the end of the conversation. It works very well.
Problem:
Now I am considering using the "StateServer mode" in the "Http Session" section. Thus, all objects in the "HTTP session" must be serializable, including the "NHibernate Session".
So, I'm doing a proof of concept to confirm that serializing / deserializing the "NHibernate Session" and its cached objects works.
"Proof of concept" is a unit test. And the goal is to get through.
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<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*/ }
The test passes almost everything. But it fails when it tries to load an object that is "Lazy".
Error:
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>
Question:
Even after reconnecting the deserialized "hibernation session", I get a "Lazy Load Error". How to avoid such a "Lazy Load Error" without having to reconnect objects?
I use:
- Spring.Net 1.3.2
- NHibernate 2.1.2
- System.Data.SQLite 1.0.80.0
Full source is here: Q11553780.7z
NOTES:
- Before opening the solution (". \ Src \ SofPOC.2010.sln") run ". \ Dependencies \ setup.bat" to load the dependencies.
- For instructions on dependencies, see ". \ Readme.txt" and ". \ Dependencies \ readme.txt".
Edition:
I found that the cause of the problem is in the NHibernate.Proxy.AbstractLazyInitializer NHibernate class. The _session field _session marked as [NonSerialized] . This does not allow serialization of this field. Therefore, after deserialization, it is zero.
See code:
namespace NHibernate.Proxy { [Serializable] public abstract class AbstractLazyInitializer : ILazyInitializer {
EDITED 2:
The cause of the problem is the [NonSerialized] attribute, because when I do the next hack, the test passes. By reflection, I am making the change of the "_session" attributes from "Private | NotSerialized" only to "Private".
Breaking into:
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(); }