NHibernate's ISession, scoped for a single WCF-call

23.09.2008 - 07:52 (1 year, 11 months, 1 week, 4 days ago)

Filed under TrivadisContent, WCF, NHibernate

I am working at a project that uses the .NET 3.5 communication stack between client & server (WCF) and they have decided to be Domain driven. In this case NHibernate (NH) is the persistence framework of choice.

The abstraction chosen for persistence are repositories (See Evans' relevatory book "Domain-Driven Design: Tackling Complexity in the Heart of Software").

When you look at NH's architecture, you will find the thread-safe Session Factory and the thread affine Session. A repository will need a session to operate against the underlying Database. According to Ayende, there are a number of bad patterns surrounding NH usage, one of which is starting a NH session per Repository, or even worse one Session per Repository call. For once, you are unable to do a transaction spanning several repositories. There, that should be enough as deterrence.

What scope to choose then for the session and how can we ensure the correct scope of a Session?

I am certainly not the first one to write about this, only recently Jimmy Bogard blogged about "Integrating StructureMap and NHibernate with WCF"

So, why do I write about it again? Because there aren't enough good practice posts surrounding NHibernate, and that way I can contribute to that matter.

I chose a slightly different route...I stated to my Customer that we will have WCF services that work per call. It means that:

  • There is no Session (That is, some sort of client session)
  • A service implementation is created and destroyed for every call
  • The scope of a service instance is the same as the scope of a request is the same as the scope we chose for the NHibernate Session.

Those assumptions allow you to simplify the procedure somewhat. Since we already use a Custom Instance Provider in order to do Dependency Injection at the service level, that Custom Instance Provider is the perfect place to set things up for a NH Session per call. Let us look at the Instance Provider:

class ServiceInstanceProvider<CONTRACT> : IInstanceProvider {
  ...
  public object GetInstance(InstanceContext instanceContext, System.ServiceModel.Channels.Message message)
  {
    var nhSessMgrExtension = instanceContext.Extensions.Find<NHibernateContextManager>();
    if (nhSessMgrExtension == null)
      instanceContext.Extensions.Add(new NHibernateContextManager());
    return kernel.Get<CONTRACT>(); //Ninject Kernel in this case, but irrelevant for this post
  }
  ...
  public void ReleaseInstance(System.ServiceModel.InstanceContext instanceContext, object instance)
  {
    var nhSessMgrExtension = instanceContext.Extensions.Find<NHibernateContextManager>();
    if (nhSessMgrExtension != null)
      instanceContext.Extensions.Remove(nhSessMgrExtension);
  }
}

Strictly speaking, we have a Session / service instance, however, see the above assumptions.

What is used here is one of the several contexts that WCF provides, Off hand you will see the OperationContext, InstanceContext as well as a RequestContext. Since technically I am scoping the NH Session to an instance, I will make an Extension to the InstanceContext with the provided Extension Mechanism ("Extensions.Add(...)"). Let us have a look at that NHContextManager:

class NHibernateContextManager : IExtension<InstanceContext>
{
  public ISession Session { get; set; }

  public void Attach(InstanceContext owner)
  {
    //We have been attached to the Current operation context from the 
    // ServiceInstanceProvider
  }

  public void Detach(InstanceContext owner)
  {
    if (Session != null)
    {
      Session.Flush();
      Session.Close();
    }
  }
}

Attach/Detach need to be implemented by contract. They are called when your extension is added / removed to the Extensions list.

The only thing left in the puzzle is to ensure proper session instantiation and reuse. Let us delegate this to a bit of infrastructure that NHibernate provides to us. We can register a class in the NH configuration that does the job of determining a Session when somebody calls sessionFactory.GetCurrentSession().

string currentSessionContextImplTypeName = 
  typeof(NHWCFSessionContext).FullName + ", " + 
  typeof(NHWCFSessionContext).Assembly.FullName;
props.Add("current_session_context_class", currentSessionContextImplTypeName);
...
var cfg = new NHibernate.Cfg.Configuration().AddProperties(props)
...
sessionFactory = cfg.BuildSessionFactory();

That class we register here needs to implement an interface and provide a constructor to which the sessionFactory will be passed. Now we can pull the bits together:

public class NHWCFSessionContext : ICurrentSessionContext
{
  public NHWCFSessionContext(ISessionFactory factory) : base()
  {
    this.factory = factory;
  }
        
  public NHibernate.ISession CurrentSession()
  {
    // Get the WCF InstanceContext:
    var contextManager = OperationContext.Current
      .InstanceContext.Extensions.Find<NHibernateContextManager>();
    if (contextManager == null)
    {
      throw new InvalidOperationException(
@"There is no context manager available. 
Check whether the NHibernateContextManager is added as InstanceContext extension. 
Also, this Session Provider only makes sense in a WCF context.");
    }

    if (contextManager.Session == null)
      contextManager.Session = factory.OpenSession();
    return contextManager.Session;
  }
}

That is pretty much it. If you have a DI container in use you can now add the bonus of registering some kind of instance provider for the ISession interface. In Ninject this would look like:

m.Bind<NHibernate.ISession>().ToProvider<NHSessionProvider>();

And the SessionProvider does nothing but say:

return sessionFactory.GetCurrentSession();

If you now instantiate a repository from the DI Container, and that repository has a dependency to ISession, it will get the Session correctly scoped for your WCF call.

A last disclaimer. The lifetime of services and such in WCF is not trivial. There are numerous options and depending on your target application the choices made here may not be your choices and the answer how your NH Session should be scoped is possibly not obvious either. But I hope that with the links provided and the info in here you can do a well-grounded choice.

kick it on DotNetKicks.com

Comments

Le grandseigneur du le Site ( 23.09.2008 - 13:58 UTC )
In the case shown your repository constructor looks like this:
class ThingyRepository : Repository<Thingy> {
  public ThingyRepository(ISession session) : base(session) { }
}
That is, in your tests you should be able to provide the ISession implementation to your liking...
Jimmy Bogard ( 23.09.2008 - 12:50 UTC )
Thanks for the post, Frank! I think I like this way better. I think I like this way better...contextual sessions seem to be the way to go. Just curious, how does this affect testing of repositories and things needing an ISession?
Neil Mosafi ( 13.10.2008 - 21:05 UTC )
Hi, nice article! Our services are singletons, not per-call. Do you know if you can use the IExtension<OperationContext> rather than IExtension<InstanceContext> and get one per call without relying on per-call service instances? Thanks Neil
Quedi ( 15.10.2008 - 18:52 UTC )
Hi Neil, I repaired your angle brackets ;) Without having it tried I would expect it to work. However, you may need a different place to initiate things. In your scenario you could maybe work along WCF Message Inspectors...those have the chance to look at a message pretty early when they come in and pretty late when they go out. There you could control the NH-Session within an OperationContext extension - I also saw a RequestContext wit the same extension mechanisms. Give it a shot with Debug Output and see which scope fits best to your scenario.
MinSou ( 17.04.2009 - 11:24 UTC )
Hi, this is the best pattern implementation i found for WCF, did you have a full source code example ? Best regards
puzzle games ( 17.02.2010 - 06:50 UTC )
This is really impressive and cool post. I think contextual sessions seem to be the way to go. Great job
Adam ( 22.02.2010 - 04:28 UTC )
Hi Frank, I have implemented code based on this blog post but have run into problems using it in a in a distributed transaction SOA scenario.
Description of problem: Service A calls - > Service B. Service A wraps the call to Service B in a using (var transactionScope = new TransactionScope()). Service B is a per call service and implements the code/pattern described in your post. It also supports Transaction flowing and the method in question is configured with [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]. An error is thrown when the session is closed in the NHibernateContextManager : IExtension class in the detach method.
public void Detach(InstanceContext owner)
{
  if (Session != null)
  {
    Session.Flush();                 
	Session.Close();
  }
} 
The error is : "Disconnect cannot be called while a transaction is in progress.".
I tried wrapping the session close in test to see if there is a current transaction but the Transaction.Current is always null.
Any ideas? Cheers Adam
Adam ( 22.02.2010 - 11:02 UTC )
I ended up adding a public Transaction CurrentTransaction { get; set; } property to the NHibernateContextManager class.
I set the value in the NHWCFSessionContext class CurrentSession() method
if (contextManager.CurrentTransaction == null)
{
  contextManager.CurrentTransaction = Transaction.Current;
}
If then check the value in the detach method of the NHibernateContextManager
if (Session != null)
{
  if (CurrentTransaction != null && CurrentTransaction.TransactionInformation.DistributedIdentifier != Guid.Empty) {
    // dont close or flush if there is a transaction still present.                     
	return;
  }
  Session.Flush();
  Session.Close();
} 
It seems to be working ok but I still need to put it through the testing paces.
Quedi ( 22.02.2010 - 14:10 UTC )
Hi Adam, thanks for sharing.
Sai Kovvuri ( 23.02.2010 - 20:32 UTC )
Need help in understanding the flow. Specifically the following:- How does NHWCFSessionContext get instantiated- which code invokes the constructor along with the factory instance? Also what is the best place to initialize the NH configuration and invoke the BuildSessionFactory
Ron. The other Ron. ( 23.02.2010 - 21:14 UTC )

Sai,
there is plenty of documentation available on where to instantiate the SessionFactory and initialize NH. It is usually done at application startup. In a WCF context it could be in the app hosting the ServiceHost, in the IIS it could be e.g. in the static constructor of a ServiceHostFactory you provide.

As to the NHWCFSessionContext: It is invoked by NHibernate itself as you provided the type to the NH configuration as shown above with the line. NH expects you to provide the shown constructor and it will happily pass into your class the Session Factory.

props.Add("current_session_context_class", currentSessionContextImplTypeName);

Hope this helps.

Sai Kovvuri ( 23.02.2010 - 21:57 UTC )

Thanks for the prompt reply but i am still not clear about the factory instance. I see that in your instructions that the BuildSessionFactor() is called after the property has been added using AddProperties to the new NH configuration object. How does know NHibernate know to use the sessionfactory instance as the constructor parameter for initializing NHWCFSessionContext. Is that a feature of DI?

Sai Kovvuri ( 24.02.2010 - 01:15 UTC )

Guys - One more question

Does NHSessionProvider need to implement IProvider and also how does it get access to the sessionfactory.

Thanks

Majordomus Bloggia ( 24.02.2010 - 08:53 UTC )

Sai,
the Session factory is usually something that exists only once, i.e. 1 NH Configuration -> 1 Session factory.

As for the ICurrentSessionContext-implementation, NH _expects you to provide a constructor accepting the Session factory. After configuration, NH should know and be capable of constructing the Session factory, which it will gladly provide into your constructor.

The NHSessionProvider is just a construct for NInject. in this model the Session factory is essentially a singleton, hence calling

sessionFactory.GetCurrentSession();

is the only important bit here.

Sai Kovvuri ( 24.02.2010 - 11:33 UTC )

Thanks a ton. I am almost there apart from one small clarification. I understand that NHSessionProvider is a construct for NInject, however I am not clear on what methods/properties etc. does NHSessionProvider class need to have. Any sample code snippets for NHSessionProvider will immensely help.

Thanks.

Thanks

Ron. The other Ron. ( 24.02.2010 - 13:15 UTC )

Sai,
I am really out of NInject these days. For StructureMap, I would probably put something like that into the DI configuration in order to get a session:

For<ISession>()
  .Use(ctx=>ctx.GetInstance<ISessionFactory>()
  .GetCurrentSession()); 

You have to let yourself guide by the API what the requirements are in NInject to provide some factory method.

Post a comment