开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜