1

これはおそらくニッチな問題ですが、誰かが私を助けてくれるかもしれません。WebサービスをASMXからWCFに移植していますが、完全にCastleActiveRecord上に構築されています。構成になんらかの奇妙な問題がないことを確認するために、NuGetの最新のCastleライブラリとNHibernateライブラリをすべて使用して、分離された再現を最初から作成しました。

私が最初にすることは、でActiveRecordを初期化することですApplication_Start。通常はweb.configインスタンスを使用しますが、これはおそらく問題ではありません。

protected void Application_Start(object sender, EventArgs e)
{
   //NHibernate.Driver.OracleClientDriver
   IDictionary<string, string> properties = new Dictionary<string, string>();

   properties.Add("connection.driver_class", "NHibernate.Driver.OracleClientDriver");
   properties.Add("dialect", "NHibernate.Dialect.Oracle10gDialect");
   properties.Add("connection.provider", "NHibernate.Connection.DriverConnectionProvider");
   properties.Add("connection.connection_string", "user Id=xxx;password=xxx;server=localhost;persist security info=True");
   properties.Add("proxyfactory.factory_class", "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle");

   InPlaceConfigurationSource source = new InPlaceConfigurationSource();
   source.IsRunningInWebApp = true;
   source.ThreadScopeInfoImplementation = typeof(Castle.ActiveRecord.Framework.Scopes.HybridWebThreadScopeInfo);

   source.Add(typeof(ActiveRecordBase), properties);

   ActiveRecordStarter.Initialize(source, typeof(Task), typeof(Project));
}

WCFではnullになるHybridWebThreadScopeInfoため、実装も使用していることに注意してください。HttpContext.Current

次に、Webサービスを実装します。

public class Service1 : IService1
{
   public string GetData(int value)
   {
      Project p;

      p = Project.Find(123M);
      var count = p.Tasks.Count(); //Force count query

      return p.GoldDate.ToString();
   }
}

私が電話するときProject.Find()、それはうまくいきます。次に、プロパティがレイジーであるため、これを呼び出しp.Tasks.Count()て新しいクエリを強制します。Tasksこれを行うと、例外が発生します。

Initializing [NHTest.Project#123]-役割のコレクションを遅延初期化できませんでした:NHTest.Project.Tasks、セッションまたはセッションが閉じられていません

これが発生している理由は、セッションスコープがないためです。ActiveRecordBase内部メソッドは、セッションが存在しない場合などにセッションを作成すると思います。これで、これを使用して手動で作成できます。

public string GetData(int value)
{
   Project p;

   using (new SessionScope())
   {
      p = Project.Find(123M);
      var count = p.Tasks.Count(); //Force count query

      return p.GoldDate.ToString();
   }
}

これはうまくいくでしょう。ただし、これはASP.NET Webサービスで完全に機能するため、すべてのコードでこれを行う必要はありません。

では、なぜASP.NETで機能するのでしょうか。

動作する理由は、ActiveRecordに。というhttpModuleが付属しているためCastle.ActiveRecord.Framework.SessionScopeWebModuleです。このモジュールは、すべてのHTTPリクエストの前に実行され、ActiveRecord内にデフォルトのセッションを作成します。ただし、このモジュールはWCFHTTP要求の前に呼び出されません。

ASP.NET互換モードはどうですか?

次を使用してASP.NET互換モードを有効にできます。

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" ... />

これにより、問題が修正されるだけでなく、WCF内のHTTP要求パイプラインへの他のアクセスも提供されます。これは1つの解決策になります。ただし、Visual StudioテストWebサーバーでは機能しますが、IIS7で互換モードを機能させることはできませんでした。さらに、最良の設計はWCFインフラストラクチャ内で完全に機能することだと思います。

私の質問:

Castle ActiveRecordは、WCFリクエスト内にセッションスコープを作成する機能を提供しますか?もしそうなら、これはどのように構成されていますか?

4

1 に答える 1

2

Castle ActiveRecordのソースコードを調べたところ、これに対する答えはノーであることがわかりました。ActiveRecordはネイティブWCFをサポートしていません。ただし、ActiveRecordが構成やセッションファクトリなどの厄介なNHibernateの詳細をすべて処理するため、これを処理するのはそれほど複雑ではありません。実際、WCF操作ごとにセッションスコープを自動的に作成することは、カスタムを実装するのと同じくらい簡単ですIDisplayMessageInspector。このメッセージインスペクターはSessionScope、要求を受信したときにオブジェクトを更新し、要求が終了したときにオブジェクトを破棄するだけです。

public class MyInspector : IDispatchMessageInspector
{
   public MyInspector()
   {
   }

   public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
   {
      return new SessionScope();
   }

   public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
   {
      SessionScope scope = correlationState as SessionScope;

      if (scope != null)
      {
         scope.Flush();
         scope.Dispose();
      }
   }
}

のコンストラクターはSessionScope、現在のセッションスコープを設定します。このスコープは、そのスレッド内の他の場所からアクセスできます。これにより、遅延読み込みやデータリーダーの受け渡しなどが簡単に機能します。また、HttpContextアイテムコレクションの使用方法と同様に、状態を使用してリクエスト中correlationStateの参照を追跡していることにも注意してください。SessionScopeSessionScopeWebModule

これは、web.configで構成することも、クラスIDispatchInspectorを介してWCFサービスに接続することもできます。Attribute

public class MyServiceBehaviorAttribute : Attribute, IServiceBehavior
{
   public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
   {
   }

   public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
   {
      foreach (ChannelDispatcher cDispatcher in serviceHostBase.ChannelDispatchers)
         foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints)
            eDispatcher.DispatchRuntime.MessageInspectors.Add(new MyInspector());

   }

   public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
   {
   }
}

次に、もちろん、サービスクラスに。でタグを付け[MyServiceBehavior]ます。

スレッドを確認してください!

ああ、そうです、これはすべて理論的には機能するIDispatchMessageInspectorかもしれませんが、実際には、操作と同じスレッドで実行されない可能性があります。これを修正するには、ActiveRecordIWebThreadScopeInfoクラスを実装する必要があります。このオブジェクトは、ActiveRecordが現在のセッションスコープを検索するときに使用されます。これは通常、HttpContextASP.NETを使用するときにを使用してキー設定されますが、HybridThreadScopeInfo各セッションスコープスタックをスレッドごとにキー設定することもできます。HttpContext.CurrentどちらもWCFでは機能しないため、ASP.NET(存在する場合)、WCF(OperationContext.Current存在する場合)、および任意のスレッドで実行している場合はスレッドごとをサポートするスーパーデュパーハイブリッド実装が必要です。

私の実装(高度にテストされていません)は、 HybridThreadScopeInfo実装に基づいており、いくつかの調整が加えられています。

public class WcfThreadScopeInfo : AbstractThreadScopeInfo, IWebThreadScopeInfo
{
   const string ActiveRecordCurrentStack = "activerecord.currentstack";

   [ThreadStatic]
   static Stack stack;

   public override Stack CurrentStack
   {
      [MethodImpl(MethodImplOptions.Synchronized)]
      get
      {
         Stack contextstack;

         if (HttpContext.Current != null) //We're running in an ASP.NET context
         {
            contextstack = HttpContext.Current.Items[ActiveRecordCurrentStack] as Stack;
            if (contextstack == null)
            {
               contextstack = new Stack();
               HttpContext.Current.Items[ActiveRecordCurrentStack] = contextstack;
            }

            return contextstack;
         }

         if (OperationContext.Current != null) //We're running in a WCF context
         {
            NHibernateContextManager ctxMgr = OperationContext.Current.InstanceContext.Extensions.Find<NHibernateContextManager>();
            if (ctxMgr == null)
            {
               ctxMgr = new NHibernateContextManager();
               OperationContext.Current.InstanceContext.Extensions.Add(ctxMgr);
            }

            return ctxMgr.ContextStack;
         }

         //Working in some random thread
         if (stack == null)
         {
            stack = new Stack();
         }

         return stack;
      }
   }
}

IExtension<InstanceContext>また、WCFが操作コンテキスト内にデータを格納するために使用する派生クラスも必要です。

public class NHibernateContextManager : IExtension<InstanceContext>
{
   public Stack ContextStack { get; private set; }

   public NHibernateContextManager()
   {
      this.ContextStack = new Stack();
   }

   public void Attach(InstanceContext owner)
   {
   }

   public void Detach(InstanceContext owner)
   {
   }
}

次に、構成ノードでプロパティを設定するか、オブジェクトを使用している場合はプロパティを設定することで、このIWebThreadScopeInfoクラスを使用するようにActiveRecordを構成できます。web.configthreadinfotype<activerecord>ThreadScopeInfoImplementationInPlaceConfigurationSource

これが誰かに役立つことを願っています!

于 2012-12-06T17:13:59.627 に答える