Thread safe issue with Castle.Facilities.NHibernateIntegration ISessionManager in Web context
So based on this question (here), of which I asked last week, I decided to go and have a look into the Castle project and use the Castle.Facilities.NHibernateIntegration facility.
I spent the best part of two days messing around with it and have come to the same issue: NHibernate Thread-Safe Sessions. I was hoping, out of the box, the built in ISessionManager was smart enough to handle threading, which is the reason why I decided to implement it.
In the very sparse documentation on that particular project it mentions that calling ISessionManager.OpenSession is much the same as calling session.GetCurrentSession. From this I gather there is no way for me to, force open, a new seperate session.
So has anyone the solution for me or any ideas how I can work with this issue?
(I know most people are going to say only work with one thread, but honestly think outside the box, some tools and routines automatically spawn a new thread. For instance, log4net and sessionstatestore. You can't just assume there will only be one thread, associated, with the current request.)
Notes:
I'm working on the web model with .NET 4 web application.
I invoke and resolve the Windsor container in the usual, documented way and let the container resolve the session manager. I do this in both threads.
Here is my Castle NHibernate config:
Code:
<facility id="nhibernate" isWeb="true" type="Castle.Facilities.NHibernateIntegration.NHibernateFacility, Castle.Facilities.NHibernateIntegration">
<factory id="nhibernate.factory">
<settings>
<item key="connection.connection_string">#{NHibernateConnectionString}</item>
<item key="connection.driver_class">#{NHibernateDriver}</item>
<item key="connection.provider">NHibernate.Connection.DriverConnectionProvider</item>
<item key="dialect">#{NHibernateDialect}</item>
<item key="generate_statistics">true</item>
<item key="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</item>
<item key="show_sql">true</item>
</settings>
<assemblies>
<assembly>Gigastence.Base.Common</assembly>
</assemblies>
</factory>
- Here is my example DAO
Code:
public class NHibernateDao : INHibernateDao
{
private ISessionManager sessionManager;
public NHibernateDao(ISessionManager sessionManager)
{
this.sessionManager = sessionManager;
}
public void Append(LoggingEvent loggingEvent)
{
using (IStatelessSession session = sessionManager.OpenStatelessSession())
{
using (ITransaction tran = session.BeginTransaction())
{
Log data = new Log
{
Id = Guid.NewGuid(),
Date = loggingEvent.TimeStamp,
Level = loggingEvent.Level.ToString(),
Logger = loggingEvent.LoggerName,
Thread = loggingEvent.ThreadName,开发者_如何转开发
Message = loggingEvent.MessageObject.ToString()
};
if (loggingEvent.ExceptionObject != null)
{
data.Exception = loggingEvent.ExceptionObject.ToString();
}
session.Insert(data);
tran.Commit();
}
}
}
}
- And how I call the DAO. Note: This is on the newly spawned thread which is out of my hands.
Code:
public class NHibenateAppender : AppenderSkeleton
{
protected override void Append(LoggingEvent loggingEvent)
{
if(IoC.IsInitialized)
{
var NHibernateLogger = IoC.Resolve<INHibernateDao>();
NHibernateLogger.Append(loggingEvent);
}
}
}
If you want full control of the session, I believe that the NHibernateFacility actually registers the underlying ISessionFactory to the Windsor kernel.
From that, you can invoke sessionFactory.OpenSession()
which I think should always return a new session.
I honestly don't really see what ISessionManager brings to the party...
Have a look at this link! https://github.com/haf/Castle.Facilities.NHibernate/wiki
It might solve your multi-threaded problems, as it differs in intention to the previous facility; this one lets you keep a session-per-transaction rather than one per request. As such, the multi-threaded issue is avoided and it would work equally well from your appender.
In the code, this is because the .Net has a CallContext static class that knows about what thread you're on (but ties it to your call context rather than thread-static).
We were running into this problem a lot when using the SessionPerWebRequest pattern and then forking worker threads, which as you say, cannot be helped in some situations.
The trick is as jishi says; instead of pulling the session from Func<ISession>
or ISessionManager
, you need to get access to ISessionFactory
.
Unfortunately for me, this wasn't as simple as injecting it through the constructor and having Windsor resolve it - it isn't registered as part of the installer as jishi said (unless I'm missing something). However, it is accessible through an installer callback:
public class NHibernateInstaller : INHibernateInstaller, IDatabaseInstaller
{
...
public void Registered(ISessionFactory factory)
{
SessionFactoryStore.Set(SessionFactoryKey, factory);
}
}
Where SessionFactoryStore is a singleton repository for storing your factories (in the case where you may have multiple factories, spread across clients, like me).
[Singleton]
public class SessionFactoryStore: ISessionFactoryStore
{
Dictionary<string, ISessionFactory> SessionFactories { get; set; }
public SessionFactoryStore()
{
SessionFactories = new Dictionary<string, ISessionFactory>();
}
public void Set(string key, ISessionFactory factory)
{
lock (SessionFactories)
{
if (!SessionFactories.ContainsKey(key)) SessionFactories.Add(key, factory);
}
}
public ISessionFactory Get(string key)
{
return SessionFactories.ContainsKey(key) ? SessionFactories[key] : null;
}
}
Then wherever you implement your unit of work pattern, or similar, just perform a test to see if you are running in a normal, or threaded state:
[PerWebRequest]
public class UnitOfWork : IUnitOfWork
{
private IGenericFactory GenericFactory { get; set; }
private Func<ISession> Session { get; set; }
private ISessionFactoryStore SessionFactoryStore { get; set; }
private ISession GetSession(bool isThreaded)
{
if (!isThreaded)
return Session();
else
return SessionFactoryStore.Get("YourFactoryKey").OpenSession();
}
public UnitOfWork(Func<ISession> session, ISessionFactoryStore sessionFactoryStore) {
Session = session;
SessionFactoryStore = sessionFactoryStore;
}
...
}
Hey presto, thread-safe ISession using NHibernateIntegration.
精彩评论