4

私は自分のアプリケーションで一種の「会話ごとのセッション」パターンの実装を使用しています。このアプローチでは、「NHibernate セッション」は「HTTP セッション」で保持されます。すべての「HTTP リクエスト」で、「NHibernate セッション」は最初に再接続され、リクエストの最後で切断されます。「NHibernate セッション」は、「会話」の終了まで「HTTP セッション」に保持されます。これは非常にうまく機能しています。

問題:

現在、「Http セッション」で「StateServer モード」を使用することを検討しています。したがって、「NHibernate セッション」を含め、「HTTP セッション」内のすべてのオブジェクトはシリアライズ可能でなければなりません。

そこで、「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*/  }

テストはほとんどすべてに合格します。ただし、「Lazy」であるエンティティを読み込もうとすると失敗します。

エラー:

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

詳細設定:

[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; }
    }
}

マスターセット:

[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; }
    }
}

詳細Ett.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」を再接続した後でも、「Lazy Load Error」が発生します。エンティティを再アタッチせずに、この種の「遅延読み込みエラー」を回避するにはどうすればよいですか?

私は使用しています:

  • Spring.Net 1.3.2
  • NHibernate 2.1.2
  • System.Data.SQLite 1.0.80.0

完全なソースはこちら: Q11553780.7z

ノート:

  • ソリューション (".\ Src\SofPOC.2010.sln") を開く前に、".\Dependencies\setup.bat" を実行して依存関係を読み込みます。
  • 依存関係については、「.\readme.txt」および「.\dependencies\readme.txt」を参照してください。

編集:

NHibernate.Proxy.AbstractLazyInitializer問題の原因は NHibernateのクラスにあることがわかりました。フィールド_sessionは としてマークされ[NonSerialized]ます。これにより、このフィールドはシリアル化されません。したがって、デシリアライズ後は null になります。

コードを参照してください。

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;
        ...

編集2:

問題の原因は実際には [NonSerialized] 属性にあります。これは、次の「ハック」を行うとテストがパスするためです。リフレクションにより、「_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();
    }
4

2 に答える 2

2

私の知る限り、試すべき3つのオプションがあります。

  1. リフレクションを使用して、逆シリアル化されたセッションで_sessionフィールドを設定します
  2. 新しいセッションを開き、それらを再接続します。これはやるのが面倒かもしれませんが、NHibernateチームが何かをするのを難しくしているのなら、おそらく彼らにはそれに対する正当な理由があると思います。
  3. 独自のILazyInitializerを作成します。ServerSideには、ここにそれに関する記事があります。自分で試したことはありません。

nr 1を試してみる場合は、機能するかどうかをお知らせください。何が起こるのか興味があります。理論的には機能するはずです。

于 2012-07-25T17:28:10.877 に答える
1

これは、@Jeroen の提案に従った私自身の回答です。

@Jeroenからの3番目の提案を選択しました:「独自の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 (ここでは、ISessionImplementor のシリアル化をサポートする ILazyInitializer 実装を作成しました):

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

于 2012-07-31T23:20:45.243 に答える