Serialization / deserialization "NHibernate Session", lazy initialization error ("StateServer mode" for clustering)

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 { /// <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:

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(); } 
+4
source share
2 answers

As far as I can tell, there are three options:

  • Using reflection to set the _session field with a deserialized session
  • Open a new session and reconnect them. It can be painful, but I think if the NHibernate team has a hard time doing something, they probably have a good reason for this.
  • Create your own ILazyInitializer. ServerSide has an article about it here . I have not tried it myself.

If you try nr 1, let us know if it works. I am interested to see what happens. Theoretically, it should work.

+2
source

This is my own answer following the @Jeroen suggestion:

I chose the third sentence given by @Jeroen: "Create your own ILAZyInitializer ..." (it worked). Demonstration:

The following is the way this was done:

SerializeSessionTest.spring.config (changes to "MySessionFactory"):

 <object id="MySessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate21"> <property name="DbProvider" ref="DbProvider"/> <property name="MappingResources"> <list> <value>assembly://SofPOC.Net4.NH2.Spring13/SofPOC.Questions.SerializeSession.Entities/MasterEtt.hbm.xml</value> <value>assembly://SofPOC.Net4.NH2.Spring13/SofPOC.Questions.SerializeSession.Entities/DetailEtt.hbm.xml</value> </list> </property> <property name="HibernateProperties"> <dictionary> <!--<entry key="connection.provider" value="AcessaDados.NHibernate.Connection.SiefDriverConnectionProvider, AcessaDados"/>--> <entry key="dialect" value="NHibernate.Dialect.SQLiteDialect"/> <entry key="connection.driver_class" value="NHibernate.Driver.SQLite20Driver"/> <entry key="current_session_context_class" value="Spring.Data.NHibernate.SpringSessionContext, Spring.Data.NHibernate21"/> <entry key="hbm2ddl.keywords" value="none"/> <entry key="query.startup_check" value="false"/> <entry key="show_sql" value="true"/> <entry key="format_sql" value="true"/> <entry key="use_outer_join" value="true"/> <entry key="bytecode.provider" value="SofPOC.Questions.SerializeSession.Spring.Data.NHibernate.Bytecode.BytecodeProviderSrlzSupport, SofPOC.Net4.NH2.Spring13"/> <entry key="proxyfactory.factory_class" value="SofPOC.Questions.SerializeSession.Spring.Data.NHibernate.Bytecode.ProxyFactoryFactorySrlzSupport, SofPOC.Net4.NH2.Spring13"/> </dictionary> </property> </object> 

LazyInitializerSrlzSupport.cs (an ILazyInitializer implementation was implemented here that supports ISessionImplementor serialization):

 /// <summary> /// Here was made ILazyInitializer implementation that supports the ISessionImplementor serialization. /// </summary> [Serializable] public class LazyInitializerSrlzSupport : global::Spring.Data.NHibernate.Bytecode.LazyInitializer, global::NHibernate.Proxy.ILazyInitializer, AopAlliance.Intercept.IMethodInterceptor { private static readonly MethodInfo exceptionInternalPreserveStackTrace; static LazyInitializerSrlzSupport() { exceptionInternalPreserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic); } /// <summary> /// TODO: /// </summary> /// <param name="entityName"></param> /// <param name="persistentClass"></param> /// <param name="id"></param> /// <param name="getIdentifierMethod"></param> /// <param name="setIdentifierMethod"></param> /// <param name="componentIdType"></param> /// <param name="session"></param> public LazyInitializerSrlzSupport( string entityName, Type persistentClass, object id, MethodInfo getIdentifierMethod, MethodInfo setIdentifierMethod, IAbstractComponentType componentIdType, ISessionImplementor session) :base(entityName, persistentClass, id, getIdentifierMethod, setIdentifierMethod, componentIdType, session) { this._sessionSrlzSupport = session; } /// <summary> /// This must be the trick. This will be serialized so that /// we can load the session in the "dynamic proxy". /// </summary> private ISessionImplementor _sessionSrlzSupport; #region ILazyInitializer Members public new void Initialize() { if (this.Session == null) { this.Session = this._sessionSrlzSupport; } base.Initialize(); } public new object GetImplementation() { this.Initialize(); return base.Target; } #endregion #region IMethodInterceptor Members object IMethodInterceptor.Invoke(IMethodInvocation invocation) { try { MethodInfo methodInfo = invocation.Method; object returnValue = base.Invoke(methodInfo, invocation.Arguments, invocation.Proxy); if (returnValue != InvokeImplementation) { return returnValue; } SafeMethod method = new SafeMethod(methodInfo); return method.Invoke(this.GetImplementation(), invocation.Arguments); } catch (TargetInvocationException ex) { exceptionInternalPreserveStackTrace.Invoke(ex.InnerException, new Object[] { }); throw ex.InnerException; } } #endregion } 

BytecodeProviderSrlzSupport.cs:

 /// <summary> /// TODO: /// </summary> public class BytecodeProviderSrlzSupport : global::Spring.Data.NHibernate.Bytecode.BytecodeProvider, global::NHibernate.Bytecode.IBytecodeProvider { private IProxyFactoryFactory proxyFactoryFactory; /// <summary> /// TODO: /// </summary> /// <param name="listableObjectFactory"></param> public BytecodeProviderSrlzSupport(IListableObjectFactory listableObjectFactory) : base(listableObjectFactory) { this.proxyFactoryFactory = new ProxyFactoryFactorySrlzSupport(); } #region IBytecodeProvider Members IProxyFactoryFactory IBytecodeProvider.ProxyFactoryFactory { get { return this.proxyFactoryFactory; } } #endregion } 

ProxyFactoryFactorySrlzSupport.cs:

 /// <summary> /// TODO: /// </summary> public class ProxyFactoryFactorySrlzSupport : global::NHibernate.Bytecode.IProxyFactoryFactory { #region IProxyFactoryFactory Members /// <summary> /// Build a proxy factory specifically for handling runtime lazy loading. /// </summary> /// <returns>The lazy-load proxy factory.</returns> public IProxyFactory BuildProxyFactory() { return new ProxyFactorySrlzSupport(); } ///<summary> ///</summary> public IProxyValidator ProxyValidator { get { return new DynProxyTypeValidator(); } } #endregion } 

ProxyFactorySrlzSupport.cs:

 /// <summary> /// TODO: /// </summary> public class ProxyFactorySrlzSupport : global::Spring.Data.NHibernate.Bytecode.ProxyFactory { private static readonly ILog log = LogManager.GetLogger(typeof(ProxyFactorySrlzSupport)); [Serializable] private class SerializableProxyFactory : global::Spring.Aop.Framework.ProxyFactory { // ensure proxy types are generated as Serializable public override bool IsSerializable { get { return true; } } } public override global::NHibernate.Proxy.INHibernateProxy GetProxy(object id, global::NHibernate.Engine.ISessionImplementor session) { try { // PersistentClass = PersistentClass.IsInterface ? typeof(object) : PersistentClass LazyInitializer initializer = new LazyInitializerSrlzSupport(EntityName, PersistentClass, id, GetIdentifierMethod, SetIdentifierMethod, ComponentIdType, session); SerializableProxyFactory proxyFactory = new SerializableProxyFactory(); proxyFactory.Interfaces = Interfaces; proxyFactory.TargetSource = initializer; proxyFactory.ProxyTargetType = IsClassProxy; proxyFactory.AddAdvice(initializer); object proxyInstance = proxyFactory.GetProxy(); return (INHibernateProxy)proxyInstance; } catch (Exception ex) { log.Error("Creating a proxy instance failed", ex); throw new HibernateException("Creating a proxy instance failed", ex); } } } 

Full source is here: Q11553780.7z

+1
source

All Articles