NHibernateFacility and session-per-WCF-operation

542 views
Skip to first unread message

Richard Dingwall

unread,
Aug 11, 2010, 7:44:48 AM8/11/10
to Castle Project Users
Hi,

I was wondering what is the current recommended way to achieve session-
per-WCF-operation lifetime with NHibernateFacility and WcfFacility?

I saw there was some work floating around here, did anything ever come
of it..?

http://groups.google.com/group/castle-project-users/browse_thread/thread/86c8c385bc1293d5/45c29a812b93f5cd

Rich

Krzysztof Koźmic

unread,
Aug 11, 2010, 7:50:11 AM8/11/10
to castle-pro...@googlegroups.com

Richard Dingwall

unread,
Aug 11, 2010, 10:03:07 AM8/11/10
to Castle Project Users
On Aug 11, 12:50 pm, Krzysztof Koźmic <krzysztof.koz...@gmail.com>
wrote:
>   Did you tryhttp://stw.castleproject.org/Windsor.WCF-Facility-Lifestyles.ashx
> ?

Yes for my services. For the session I am doing this:

windsor.Register(
Component.For<ISession>().LifeStyle.PerWcfOperation()
.UsingFactoryMethod(x =>
windsor.Resolve<ISessionManager>().OpenSession()));

That okay? It won't flush only dispose like this.

My question is, is there something in NHibernateFacility/WcfFacility
that takes care of this stuff?

Rich

Krzysztof Koźmic

unread,
Aug 11, 2010, 5:41:11 PM8/11/10
to castle-pro...@googlegroups.com
Not sure... you might need to add a custom decommission concern for
that...

Richard Dingwall

unread,
Aug 12, 2010, 4:20:44 AM8/12/10
to Castle Project Users
On Aug 11, 10:41 pm, Krzysztof Koźmic <krzysztof.koz...@gmail.com>
wrote:
>   Not sure... you might need to add a custom decommission concern for
> that...

It seems like a fairly common use case. I am surprised if castle
doesn't support it and I have to write my own?

- ISession injected into services
- ISession and Services last per wcf operation
- ISession flushed at the end of a WCF operation (or tx rolled back if
there was an error)

Rich

Adam Toseland

unread,
Aug 12, 2010, 4:17:08 PM8/12/10
to castle-pro...@googlegroups.com
Hi Rich,

I ended up implementing a solution for this based on this post.

http://realfiction.net/go/133
Cheers
Adam,



--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To post to this group, send email to castle-pro...@googlegroups.com.
To unsubscribe from this group, send email to castle-project-u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/castle-project-users?hl=en.


Richard Dingwall

unread,
Aug 17, 2010, 8:45:13 AM8/17/10
to Castle Project Users
On Aug 12, 9:17 pm, Adam Toseland <a...@razorit.com.au> wrote:
> Hi Rich,
>
> I ended up implementing a solution for this based on this post.
>
> http://realfiction.net/go/133
> Cheers
> Adam,

Thanks, I started with your implementation but found a way that didn't
require any extra code:

http://richarddingwall.name/2010/08/17/one-nhibernate-session-per-wcf-operation-the-easy-way/

Rich

Berke Sokhan

unread,
May 23, 2011, 9:29:49 AM5/23/11
to castle-pro...@googlegroups.com, hen...@haf.se
Hello,

I am trying to achive the same thing with new Castle.Facilites.NHibernate using one of the Richard's sources at his blog post article: http://candland.net/blog/2009/10/27/nhibernate-session-per-request-using-castles-wcffacility/

(I didnt choose to use Richard's method because it needs to decorate service classes with [Transaction] attribute, which I would not prefer.)

The problem is new ISessionManager.Open doesnt take a string but instead SessionManager's ctor takes a Func<ISession>, so the highlighted section in the following does not work:

public class WcfSessionPerRequestCallContextInitializer : ICallContextInitializer
    {
        private readonly ISessionManager sessionManager;
 
        private readonly string[] dbAliases;
 
        public WcfSessionPerRequestCallContextInitializer(ISessionManager sessionManager, string[] dbAliases)
        {
            this.sessionManager = sessionManager;
            this.dbAliases = dbAliases;
        }
 
        public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
        {
            var extension = new NHibernateContextExtension();
            foreach (var dbAlias in dbAliases)
            {
                extension.Sessions.Add(sessionManager.OpenSession(dbAlias));
            }
            instanceContext.Extensions.Add(extension);
            return extension;
        }
 
        public void AfterInvoke(object correlationState)
        {
            if (correlationState != null)
            {
                ((IDisposable)correlationState).Dispose();
            }
        }
    }

- Can you give some pointers on how to enable per WCF operation session with new Castle.Facilities.NHibernate?

- And also I use WcfIntegration to host services, do I have an option to use WcfIntegration to solve this issue with a different implementation?


Thanks,
Berke Sökhan.

2010/8/17 Richard Dingwall <rdin...@gmail.com>

Rich

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.
To post to this group, send email to castle-pro...@googlegroups.com.
To unsubscribe from this group, send email to castle-project-u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/castle-project-users?hl=en.




--
Berke SOKHAN.

http://twitter.com/berkesokhan
http://blog.berkesokhan.com
http://www.birliktegelistir.com/editors.aspx

Henrik Feldt

unread,
May 23, 2011, 11:45:47 AM5/23/11
to castle-pro...@googlegroups.com

Hello,

 

Beware that for version 3 (which is btw not official from the powers that be), the API is different.

 

The aim of the new is to use IoC to a greater extent and hand-crafted code to a lesser.

 

The code that selects database looks like this:

 

Component.For<ISessionManager>().Instance(new SessionManager(() => {

                             var factory = Kernel.Resolve<ISessionFactory>(x.Instance.SessionFactoryKey);

                             var s = x.Instance.Interceptor.Do(y => factory.OpenSession(y)).OrDefault(factory.OpenSession());

                             s.FlushMode = _FlushMode;

                             return s;

}))

.Named(x.Instance.SessionFactoryKey + SessionManagerSuffix)

.LifeStyle.Singleton

 

So when you follow:

https://github.com/haf/Castle.Facilities.NHibernate/wiki/NHibernate-Facility---Quick-Start

 

and implement the INHibernateInstaller, then you can choose the session factory key.

 

When you then register your WcfSessionPerRequestCallContextInitializer, you can choose what ISessionManager instance you depend on.

 

If you choose to go the manual way with Transactions, beware that you handle the edge-cases well. Or even better; tell me what type of API you want to see for release version 3.0, which doesn’t use AOP, but allows you to call a ‘transaction handler’ of some sort.

 

Cheers

Henrik Feldt

unread,
May 23, 2011, 12:03:10 PM5/23/11
to castle-pro...@googlegroups.com

PS;

 

It’s actually version 0.2 of NHFac, 3.0.0.2007 of tx.

 

…and there’s a new concept which is default; session per transaction, which is what you are looking for (per operation as you say yourself)!

 

From: castle-pro...@googlegroups.com [mailto:castle-pro...@googlegroups.com] On Behalf Of Berke Sokhan
Sent: den 23 maj 2011 15:30
To: castle-pro...@googlegroups.com
Cc: hen...@haf.se
Subject: Re: NHibernateFacility and session-per-WCF-operation

 

Hello,

Berke Sokhan

unread,
May 23, 2011, 1:32:03 PM5/23/11
to castle-pro...@googlegroups.com, Henrik Feldt
Hello Henrik,

Thanks for the answer.

I was just inspecting your codebase and noticed "SessionPerTransaction" and "SessionPerWebRequest", and I would ask a "session for per wcf operation" as I am hosting my app in a windows service with tcpNetBindings :). No need for that now.

But looking at your wiki quick start example, I saw that we have some different approaches to achive auto tx management. I strive to maintain my service classes infrastructure agnostic. But your examples injects Func<ISession> to service ctor (which can be pulled to a repository in another example, so that's not the problem, but also uses Transaction attribute, which cannot be pulled to another layer.
 
I would appreciate if you'd help me achive my "clean" approach as I am not quite knowledgeable in WCF lifecycle.

1.
At the orginial post by Dusty Candland (http://candland.net/blog/2009/10/27/nhibernate-session-per-request-using-castles-wcffacility/), he creates WcfSessionPerRequestCallContex with the new keyword (as highligted below) :
public class WcfSessionPerRequestBehavior : IServiceBehavior
    {
        private readonly ISessionManager sessionManager;
 
        private string[] dbAliases = { "nh.facility.default" };
 
        public string[] DbAliases
        {
            get { return dbAliases; }
            set { dbAliases = value; }
        }
 
        public WcfSessionPerRequestBehavior(ISessionManager sessionManager)
        {
            this.sessionManager = sessionManager;
        }
 
        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
 
        }
 
        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
 
        }
 
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (var cdb in serviceHostBase.ChannelDispatchers)
            {
                var channelDispatcher = cdb as ChannelDispatcher;
 
                if (null != channelDispatcher)
                {
                    foreach (var endpointDispatcher in channelDispatcher.Endpoints)
                    {
                        foreach (var dispatchOperation in endpointDispatcher.DispatchRuntime.Operations)
                        {
                            dispatchOperation.CallContextInitializers.Add(new WcfSessionPerRequestCallContextInitializer(sessionManager, dbAliases));
                        }
                    }
                }
            }
        }
    }

Do you suggest that I should have resolve it from the container but register as follows
.Register(Component.For<ICallContextInitializer>().ImplementedBy<WcfSessionPerRequestCallContextInitializer>().LifeStyle.PerWcfOperation())
?


2. With the first item implemented then, at the following class, if I remove "string [] dbAliases" injection (because injected sessionManager would be the right one provided by your NH facilities selector) and also remove the foreach to traverse the string array, then can I be able use your NH facility. In short, red highlighted must be removed, right?

public class WcfSessionPerRequestCallContextInitializer : ICallContextInitializer
    {
        private readonly ISessionManager sessionManager;
 
        private readonly string[] dbAliases;
 
        public WcfSessionPerRequestCallContextInitializer(ISessionManager sessionManager, string[] dbAliases)
        {
            this.sessionManager = sessionManager;
            this.dbAliases = dbAliases;
        }
 
        public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
        {
            var extension = new NHibernateContextExtension();
            foreach (var dbAlias in dbAliases)
            {
                extension.Sessions.Add(sessionManager.OpenSession(dbAlias));
            }
            instanceContext.Extensions.Add(extension);
            return extension;
        }
 
        public void AfterInvoke(object correlationState)
        {
            if (correlationState != null)
            {
                ((IDisposable)correlationState).Dispose();
            }
        }
    }
Thanks again for your interest.


2011/5/23 Henrik Feldt <hen...@haf.se>

Berke Sokhan

unread,
May 24, 2011, 10:21:26 AM5/24/11
to Henrik Feldt, castle-pro...@googlegroups.com, nhu...@googlegroups.com
Hello Henrik,

Yes, I want to have a session per wcf operation and also same tx for a wcf operation (except logging purposed interception mechanisms utilizing Castle Windsor's IInterceptor, but that's another issue for now)

To start and commit the transaction I use NHibernateContextExtension and WcfSessionPerRequestCallContextInitializer. So the latest classes are like those:

public class WcfSessionPerRequestCallContextInitializer : ICallContextInitializer
    {
        private readonly ISessionManager
 sessionManager;
 
        public WcfSessionPerRequestCallContextInitializer(ISessionManager sessionManager)
        {
            this.sessionManager = sessionManager;
        }
 
        public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
        {
            var extension = new NHibernateContextExtension();
 
            NHibernate.ISession session = sessionManager.OpenSession();
            session.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);
            extension.Sessions.Add(session);
 
            instanceContext.Extensions.Add(extension);
            return extension;
        }
 
        public void AfterInvoke(object correlationState)
        {
            if (correlationState != null)
            {
                ((IDisposable)correlationState).Dispose();
            }
        }
    }

public class WcfSessionPerRequestBehavior : IServiceBehavior
    {
        private readonly ISessionManager
 sessionManager;
        
        private readonly ICallContextInitializer callContextInitializer;
 
        public WcfSessionPerRequestBehavior(ISessionManager sessionManager, ICallContextInitializer callContextInitializer)
        {
            this.sessionManager = sessionManager;
            this.callContextInitializer = callContextInitializer;
        }
 
        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
 
        }
 
        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
 
        }
 
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (var cdb in serviceHostBase.ChannelDispatchers)
            {
                var channelDispatcher = cdb as ChannelDispatcher;
 
                if (null != channelDispatcher)
                {
                    foreach (var endpointDispatcher in channelDispatcher.Endpoints)
                    {
                        foreach (var dispatchOperation in endpointDispatcher.DispatchRuntime.Operations)
                        {
                            dispatchOperation.CallContextInitializers.Add(callContextInitializer);
                        }
                    }
                }
            }
        }
    }

public sealed class NHibernateContextExtension : IExtension<InstanceContext>, IDisposable     {         public List<ISession> Sessions { getset; }         public NHibernateContextExtension()         {             Sessions = new List<ISession>();         }         public void Attach(InstanceContext owner)         {          }         public void Detach(InstanceContext owner)         {                     }         public void Dispose()         {             if (Sessions != null)             {                 Sessions.ForEach(s => s.Transaction.Commit());                 Sessions.ForEach(s => s.Close());             }         }     }


public class Repository : IRepository     {         protected Func<ISession> sessionStrategy;         public Repository(Func<ISession> sessionStrategy)         {             this.sessionStrategy = sessionStrategy;         }         public T GetById<T>(object id)         {             T objectFound = sessionStrategy.Invoke().Get<T>(id);             return objectFound;         }
}



Container                 .AddFacility<WcfFacility>()                 .AddFacility<AutoTxFacility>()                 .AddFacility<NHibernateFacility>()                                 .Register(Component.For<INHibernateInstaller>().ImplementedBy<DatabaseMapInstaller>().LifeStyle.Singleton)                 .Register(                     Component.For<IServiceBehavior>().ImplementedBy<WcfSessionPerRequestBehavior>()                         .LifeStyle.PerWcfOperation())                                  .Register(                     Component.For<ICallContextInitializer>().ImplementedBy<WcfSessionPerRequestCallContextInitializer>()                         .LifeStyle.PerWcfOperation())


- Service classes are agnostic to all of these. They are registered to container with their interfaces and hosted by wcf integration facility.
So my last question would be, is it certain that ISessionManagers that will be injected to WcfSessionPerRequestBehavior and WcfSessionPerRequestCallContextInitializer are really different instances for "per wcf operation" (or session per trnx as you would say)?

2011/5/24 Henrik Feldt <hen...@haf.se>

Quick answer;

 

I think I see your point.

 

You want to have a session per wcf operation, and that is the point of your code.

 

I would ask you to consider what business/transaction boundary that you are trying to delineate – often if you are using a relational database you care about your consistency, what you are looking for is a transaction and because NHibernate is a ORM intimately bound to transactions, session-per-tx is what you are looking for. I think.

 

Having attributes doesn’t couple your code in any negative way. They can be removed without affecting the control flow.

 

It seems your changes are ok, but you will have to have a transasction there somewhere.

 

Cheers,
Henrik

 

From: Berke Sokhan [mailto:berke...@gmail.com]
Sent: den 23 maj 2011 19:32

Cc: Henrik Feldt

Henrik Feldt

unread,
May 24, 2011, 2:09:31 PM5/24/11
to castle-pro...@googlegroups.com

Hi Berke,

 

I can garantuee that ISessionManager is exactly the same instance all the time, because it’s a singleton.

 

The ISession it is a factory of, however, is not. It is per transaction per default – if you choose to use it differently, like you are to make it ‘cleaner’, you will have to handle the release of it. That is not handled for you – don’t just dispose it, also release it from the MicroKernel/Windsor in your dispose method.

 

By default the lifestyle requires an ambient transaction. If you want to handle transactions yourself, please set the ambient transaction (pseudo-code):

 

NHibernate.ITransaction tx = ses…Begin(…);

using (new TxScope( new CommittableTransaction().EnlistDurable(tx.SqlTransaction : IDbTransaction)))

{// perform call

}   // tx scope is disposed and ambient tx reset.

 

This is among other things what the tx project does for you. (besides handling the error conditions which you code doesn’t – what happens if your tx deadlocks or livelocks? Your session won’t be disposed until GC/Finialization, right? A quick hack would be a try{}finally{} in your Dispose() method, to fix that problem.

 

In any case; you want to use SQL Server as your tx coordinator and not LTM, so: then you need to wire up services depending on ISessionManager differently, pseudo code again:

 

Component.Register<ICallContextInitializer>( … ).Depends(Key.Name(“<<your session manager key>>” + NHibernateFacility.SessionManagerSuffix))

 

.. same for IServiceBehavior.

 

And then your ISession will be resolved with a transient lifestyle.

 

PS: Also, if you have it like this, calls to other services won’t join your transaction unless they are in the same app domain (and hence same SqlConnection pool), so you won’t transaction flow out of the box, because you don’t set the ambient tx.

 

Cheers,
Henrik

Henrik Feldt

unread,
May 24, 2011, 2:16:34 PM5/24/11
to castle-pro...@googlegroups.com

PS Ah; in terms of transactions;

 

public ctor(ITransactionManager m)

{

this.m = m;

}

 

 

public object BeforeInvoke(…) { this.m.BeginTransaction(); }

 

This is a lot better than using

session.BeginTransaction

 

It simple handles more error cases for you.

 

Cheers,

Henrik

 

 

 

From: castle-pro...@googlegroups.com [mailto:castle-pro...@googlegroups.com] On Behalf Of Berke Sokhan


Sent: den 24 maj 2011 16:21
To: Henrik Feldt; castle-pro...@googlegroups.com; nhu...@googlegroups.com

Berke Sokhan

unread,
May 24, 2011, 4:51:27 PM5/24/11
to castle-pro...@googlegroups.com, nhu...@googlegroups.com, Henrik Feldt
Ah, of course I should be thinking ISessionManager as a substitute for ISessionFactory as it is creating the ISession, and it is not the ISession itself... so it should be natural that it's lifecycle is singleton.

And I will be considering your comments that I do most of the plumbing code and use only minimal features of the NH facility.

I think I need to project on myself what you ve said a little more and try pther variations.

When my code is completed I'll share with the community.

Thanks for your time and comments Henrik!

2011/5/24 Henrik Feldt <hen...@haf.se>

Henrik Feldt

unread,
May 24, 2011, 4:58:30 PM5/24/11
to castle-pro...@googlegroups.com

Hi!

 

No problem, glad to help! It would be great if your experiences translate to a better experience using these facilities, by you sharing them with us.

 

Regards,

Zachariah Young

unread,
Apr 22, 2012, 4:11:40 PM4/22/12
to castle-pro...@googlegroups.com
Does anyone have a working example of this post.  Would be really helpful.

2011/5/24 Henrik Feldt <hen...@haf.se>

2011/5/23 Henrik Feldt <hen...@haf.se>

To post to this group, send email to castle-project-users@googlegroups.com.
To unsubscribe from this group, send email to castle-project-users+unsub...@googlegroups.com.


For more options, visit this group at http://groups.google.com/group/castle-project-users?hl=en.

--

You received this message because you are subscribed to the Google Groups "Castle Project Users" group.

To post to this group, send email to castle-project-users@googlegroups.com.
To unsubscribe from this group, send email to castle-project-users+unsub...@googlegroups.com.


For more options, visit this group at http://groups.google.com/group/castle-project-users?hl=en.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.

To post to this group, send email to castle-project-users@googlegroups.com.
To unsubscribe from this group, send email to castle-project-users+unsub...@googlegroups.com.


For more options, visit this group at http://groups.google.com/group/castle-project-users?hl=en.

--

You received this message because you are subscribed to the Google Groups "Castle Project Users" group.

To post to this group, send email to castle-project-users@googlegroups.com.
To unsubscribe from this group, send email to castle-project-users+unsub...@googlegroups.com.


For more options, visit this group at http://groups.google.com/group/castle-project-users?hl=en.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.

To post to this group, send email to castle-project-users@googlegroups.com.
To unsubscribe from this group, send email to castle-project-users+unsub...@googlegroups.com.


For more options, visit this group at http://groups.google.com/group/castle-project-users?hl=en.

--
You received this message because you are subscribed to the Google Groups "Castle Project Users" group.

To post to this group, send email to castle-project-users@googlegroups.com.
To unsubscribe from this group, send email to castle-project-users+unsub...@googlegroups.com.

Reply all
Reply to author
Forward
0 new messages