2

I've got a problem with NHibernate 3.2.

I'm porting a solution we used in a Java application, to a c# 4.0 application. What we want to create is a simple mechanism that handle session and transaction through the NHibernate SessionFactory, letting the transaction be instantiated by the unit-of-work beginner, and then being used by all the partecipant method, without even know that they are part of a bigger unit-of-work. But if you call these sub methods directly, they'll handle their own transaction.

In these question I explained better our approach. We first did it in Java world, and it works pretty fine. Now I'm porting the the same approach to c# 4.0, using NHibernate 3.2.

The class which is going to handle all my session and transaction is colled OperationManager (you could think to UnitOfWorkManager):

public class OperationManager : IDisposable
{
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

    ITransaction tx = null;
    ISession session = null;
    bool isInternalTransaction = false;

    public ISession BeginOperation()
    {
        logger.Debug("Thread : " + System.Threading.Thread.CurrentThread.ManagedThreadId);
        session = PersistenceManager.Istance.GetSession();
        if (session.Transaction.IsActive)
        {
            isInternalTransaction = false;
            tx = session.Transaction;                               
            logger.Debug(GetCallerClassDotMethod() + " is binding to transaction " + tx.GetHashCode());
        }
        else
        {
            isInternalTransaction = true;
            tx = session.Transaction;
            tx.Begin(); 
            logger.Debug("Transaction " + tx.GetHashCode() + " created by " + GetCallerClassDotMethod());
        }
        logger.Debug("Session hash " + session.GetHashCode());
        return session;
    }

    private String GetCallerClassDotMethod() {
        // needed to get the Business Logic method calling the public methods
        var st = new StackTrace();
        var sf = st.GetFrame(2);
        var methodReference = sf.GetMethod().Name;
        var classReference = sf.GetMethod().DeclaringType.FullName;
        return string.Concat(classReference, ".", methodReference);
    }

    public void CommitOperation()
    {
        if (isInternalTransaction)
        {                
            tx.Commit();
            logger.Debug(GetCallerClassDotMethod() + " is committing transaction " + tx.GetHashCode());
        }
    }

    public void RollbackOperation()
    {
        if (isInternalTransaction)
        {
            tx.Rollback();                
            logger.Debug(GetCallerClassDotMethod() + " is rolling back transaction " + tx.GetHashCode());                
        }
    }

    public void Dispose()
    {
        tx.Dispose();
        session.Dispose();
    }
}

Here's my PersistenceManager

internal class PersistenceManager : IDisposable
{
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
    private static PersistenceManager _istance;
    private ISessionFactory _SessionFactory;
    private static Object _lock = new Object();

    public static PersistenceManager Istance
    {
        get
        {
            lock (_lock)
            {
                if (_istance == null)
                {
                    _istance = new PersistenceManager();
                    logger.Info("New PersistenceManager instance created");
                }
                return _istance;
            }
        }
    }

    private PersistenceManager()
    {
        // Initialize
        Configuration cfg = new Configuration();
        cfg.Configure(ConfigurationManager.ConfigurationManager.Istance.hibernateConfiguration);
        cfg.SetProperty("connection.connection_string", ConfigurationManager.ConfigurationManager.Istance.connectionString);

        /* Note: The AddAssembly() method requires that mappings be 
         * contained in hbm.xml files whose BuildAction properties 
         * are set to ‘Embedded Resource’. */

        // Add class mappings to configuration object
        System.Reflection.Assembly thisAssembly = typeof(PersistenceManager).Assembly;
        cfg.AddAssembly(thisAssembly);            

        // Create session factory from configuration object
        _SessionFactory = cfg.BuildSessionFactory();
    }



    public void Dispose()
    {
        _SessionFactory.Dispose();
    }


    /// <summary>
    /// Close this Persistence Manager and release all resources (connection pools, etc). It is the responsibility of the application to ensure that there are no open Sessions before calling Close().
    /// </summary>
    public void Close()
    {
        _SessionFactory.Close();
    }


    public ISession GetSession()
    {
        return _SessionFactory.OpenSession();
    }

}

Now, whenever I need to access DB, I use an OperationManager instance to grab the current session (and current transaction) and use it for al my need. An example is found here:

public IList<Agency> getAllAgencies()
    {
        using (var om = new OperationManager())
        {
            try
            {
                om.BeginOperation();
                var result = base.Load<Agency>().ToList();
                om.CommitOperation();
                return result;
            }
            catch (Exception ex)
            {
                om.RollbackOperation();
                throw ex;
            }
        }
    }

and in the base class I have

protected IQueryable<T> Load<T>() where T : Model.ModelEntity
    {
        using (var om = new OperationManager())
        {
            try
            {
                var session = om.BeginOperation();
                var entities = session.Query<T>();
                om.CommitOperation();
                return entities;
            }
            catch (Exception ex)
            {
                om.RollbackOperation();
                throw new Exception(msg, ex);
            }
        }
    }

The problem is that, even if I configured NHibernate session factory to work on a per-thread model using <property name="current_session_context_class">thread_static</property>, the two calls to OperationManager.beginOperation() give back a difference sessione, thus with different transaction.

Can anybody tell me why this is happening?

Edited: Following the suggestion of Fredy Treboux, I tried to implement a mechanism that create a new session or just get the current one, using CurrentSessionContext static object of NHibernate. Too bad, this still don't work. After cleaning up the code, avoiding everything related on transaction, session, unit-of-work, etc, I wrote a very trivial class, and I figured out that using

<property name="current_session_context_class">thread_static</property> 

bring me a problem with sql server 2008 db. Using that context class, on a classic SessionFactory.OpenSession() approach and then loading some data, I get the following error:

System.Data.SqlClient.SqlException: A transport-level error has occurred when receiving results from the server. (provider: Shared Memory Provider, error: 0 - Invalid Handle.)

Any idea why this happen?

Community
  • 1
  • 1
themarcuz
  • 2,573
  • 6
  • 36
  • 53
  • 1
    Look at this for an example on how to use contextual sessions within NH: http://stackoverflow.com/questions/7454589/no-session-bound-to-the-current-context/7458905#7458905 – Cole W Nov 16 '11 at 13:01

1 Answers1

2

You are calling SessionFactory.OpenSession() every time. This will open and return a new session regardless of everything else.

Altough I won't be eager to recommend this approach, for it to work, you'll need to do some sort of reference counting on your PersistenceManager to know when to open a new session and when to release it.

The current_session_context_class is not affecting this, since it only controls what SessionFactory.GetCurrentSession() returns, and the ThreadStaticSessionContext (thread_static) has to be manipulated manually anyways through Bind/Unbind.

Approach I would recoomend

Well, two things.

First, I like to define a specific layer to handle sessions/transactions. What I'm trying to say is that if I got methods A and B, and A may use B but B may also be used from outside I would prefer to have a method C that defines the session/transaction boundary for B.

So, A and C are the methods that are publicly available, and both use B (even when in C you're almost directly calling B).

The second thing is that you may be better of using a session context manager already available like the ones you can find in many NHibernate based libraries (e.g. http://code.google.com/p/unhaddins/) or even if you decide to go on implementing your own, try to make it fit the SessionContext machinery available in NHibernate so you'll be able to call SessionFactory.GetCurrentSession() to get the session and be compatible with other contexts/methods of doing the same.

Fredy Treboux
  • 3,167
  • 2
  • 26
  • 32
  • may I ask what approch will you recommend? I'm trying to find the best way, so any suggestion is appreciated – themarcuz Nov 15 '11 at 20:49
  • yes it is... mostly. But I want to keep the business layer agnostic about being used in web application or not. I'm going to use it also in some windows service for example. So I want to avoid the famous session-per-request pattern. Anyway, following your suggestion I came up with another error, related to the thread_static approach. Edited original question – themarcuz Nov 15 '11 at 23:29
  • 1
    You don't need to avoid session-per-request to make the business layer agnostic. That's precisely the point of using a CurrentSessionContext and SessionFactory.GetCurrentSession(). You can just give the ISessionFactory to your business objects and all they should need to do is to call SessionFactory.GetCurrentSession() to get a session. Then you just configure the appropriate context according to the situation, which will be outside of your business layer and in web it may very well be session-per-request. Sorry, no clue about you new error,are you sure it's related to thread_static like that? – Fredy Treboux Nov 16 '11 at 10:36
  • Today I have no more problem related to the thread_static stuff. So I've been able to apply your suggestion. Everything works fine now. I don't know what happens yesterday, but something had to be wrong between NH and SQLServer. Rebooting the machine everything went ok. Thanks for your help. – themarcuz Nov 16 '11 at 21:37