2012-07-19 54 views
4

我在我的應用程序中使用了一種「會話每會話」模式實現。 在這種方法中,「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(); 
    } 

回答

2

據我所知,有三個選項嘗試:

  1. 使用反射來設置_session場與反序列化會話
  2. 打開的新的會議重新附加他們。這可能是一件很痛苦的事情,但我認爲如果NHibernate團隊很難做到這一點,他們可能會有一個很好的理由。
  3. 創建您自己的ILazyInitializer。 ServerSide有一篇關於它的文章here。我沒有自己嘗試過。

如果您嘗試nr 1,請告訴我們它是否有效。我很想看看會發生什麼。理論上它應該工作。

+0

感謝您的幫助。我想測試三個選項。我有一個考慮要做:aboult「...即使它沒有標記NonSerializable。」我認爲你在這一點上是錯誤的。使用反射,我在「_session」的FieldInfo中將其從「Private | NotSerialized」更改爲僅「Private」。它的工作。 – Hailton 2012-07-25 21:40:20

+0

第三種解決方案似乎更具吸引力。第一個解決方案似乎有點不切實際,因爲我必須確定「動態代理」的實例,但我同意它的工作原理。第二種解決方案正是我想要避免的,這就是爲什麼我使用「會話每會話」模式。 – Hailton 2012-07-26 00:17:34

+1

現在,我在白天再次閱讀我的答案,「如果反序列化會話,它不會神奇地出現在_session字段中(即使它未標記爲NonSerializable)。」沒有任何意義,所以我從答案中刪除了它。 – Jeroen 2012-07-30 13:51:22

1

這是我自己的建議,通過@Jeroen以下的答案:

我選擇通過@Jeroen給出的第三個建議:「創建您自己ILazyInitializer ......」(它的工作)。 演示:

下面是它被做的方式:

SerializeSessionTest.spring.config(在 '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(這裏有人ILazyInitializer實施支持ISessionImplementor序列化):

/// <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); 
     } 
    } 
} 

完整的數據來源是:Q11553780.7z