Castle ActiveRecord没有初始化与WCF的会话

时间:2012-12-03 19:43:05

标签: .net wcf nhibernate castle-activerecord

这可能是一个利基问题,但也许有人可以帮助我。我将我的Web服务从ASMX移植到WCF,但是我完全基于Castle ActiveRecord。为了确保我的配置中没有某些奇怪的问题,我使用NuGet中所有最新的Castle和NHibernate库从头开始构建了一个独立的repro。

我要做的第一件事就是在Application_Start中初始化ActiveRecord。通常我会使用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));
}

注意我也在使用HybridWebThreadScopeInfo实现,因为HttpContext.Current在WCF中将为null。

接下来,我实现了我的网络服务:

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属性是惰性的。当我这样做时,我得到了例外:

初始化[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附带了一个名为Castle.ActiveRecord.Framework.SessionScopeWebModule的httpModule。此模块在每个HTTP请求之前运行,并在ActiveRecord中创建默认会话。但是,在WCF HTTP请求之前,此模块未被 调用。

ASP.NET兼容模式怎么样?

您可以使用以下方式启用ASP.NET兼容模式:

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

这也将解决问题,并提供对WCF内HTTP请求管道的其他访问。这将是一个解决方案。但是,虽然它适用于Visual Studio测试Web服务器,但我从未能够在IIS7上使用兼容模式。另外,我觉得最好的设计是完全在WCF基础设施中工作。

我的问题:

Castle ActiveRecord是否提供在WCF请求中创建会话范围的功能?如果是这样,这是如何配置的?

1 个答案:

答案 0 :(得分:1)

在浏览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的构造函数将设置当前会话范围,然后可以在该线程中的任何其他位置访问该范围。这使得延迟加载和传递数据读取器等工作变得简单。您还会注意到,我在请求期间使用correlationState状态跟踪SessionScope的引用,类似于SessionScopeWebModule如何使用HttpContext项集合。

IDispatchInspector可以在web.config中配置,或通过Attribute类附加到WCF服务:

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]标记服务类。

Ack主题!

啊,是的,这一切都可能在理论上有效,但实际上,IDispatchMessageInspector可能不会与操作在同一个线程中运行。要解决此问题,我们需要实现一个ActiveRecord IWebThreadScopeInfo类。当ActiveRecord查找当前会话范围时,将使用此对象。这通常在使用ASP.NET时使用HttpContext键控,但HybridThreadScopeInfo也能够按线程键入每个会话范围堆栈。这两个都不适用于WCF,所以我们需要一个支持ASP.NET(当HttpContext.Current存在时),WCF(当OperationContext.Current存在时)和每个线程的超级duper-hybrid实现。 '只是在某个任意线程中运行。

我的实施(未经过高度测试)基于HybridThreadScopeInfo implementation并进行了一些调整:

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配置节点或web.config属性上设置threadinfotype属性,将ActiveRecord配置为在<activerecord>中使用此ThreadScopeInfoImplementation类如果您使用的是InPlaceConfigurationSource对象。

希望这有助于某人!