views:

100

answers:

1

I have scoured the web and have yet to find and example that matches my requirements.

What I have is an existing multi-tenant asp.net application where all users authenticate against a single SQL Server database. This database also contains several other settings type data that is used within the application. Each client after authentication, utilizes thier own SQL Server database for data storage, for isolation purposes. Essentially all of the client database are identical and reside on the same server, but reside on one or more servers as well.

The application is currently written in asp.net 2.5 framework and utilizes the Micrsoft Practices Enterprise Library for DAL. Wnd we are looking to migrate to 4.0 and implement NHibernate to replace the MPEL.

I have implemented a solution already using NHibernate and the 4.0 framework, so I am familar with the concepts. I found the resources for my current session manager here as a matter of fact. But that application only had a single database, so not much too it. The implementation is essentially what you see here: http://www.lostechies.com/blogs/nelson_montalvo/archive/2007/03/30/simple-nhibernate-example-part-4-session-management.aspx

The other solutions that I have seen suggest multiple config entries and/or files to manage this, but that is not desireable, since we may add new clients frequently and all of the connection information is already maintained in the authentication database.

Does anyone have any suggestions on how I might be able to pass in the client's connection string to a session manager?

Thanks


EDIT


Perhaps a code example would be helpfull here..

The following is my current session manager class, which is based on the article mentioned above.

using System;

using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.Remoting.Messaging; using System.Web; using NHibernate; using NHibernate.Cfg; using NHibernate.Cache; using singlepoint.timeclock.domain;

namespace singlepoint.timeclock.repositories { /// /// Handles creation and management of sessions and transactions. It is a singleton because /// building the initial session factory is very expensive. Inspiration for this class came /// from Chapter 8 of Hibernate in Action by Bauer and King. Although it is a sealed singleton /// you can use TypeMock (http://www.typemock.com) for more flexible testing. /// public sealed class nHibernateSessionManager { private ISessionFactory idadSessionFactory; private ISessionFactory clientSessionFactory; private string _client;

    #region Thread-safe, lazy Singleton
    // lazy initialisation, therefore initialised to null
    private static nHibernateSessionManager instance = null;


    /// <summary>
    /// This is a thread-safe, lazy singleton.  See http://www.yoda.arachsys.com/csharp/singleton.html
    /// for more details about its implementation.
    /// </summary>
    public static nHibernateSessionManager Instance
    {
        get { return GetInstance(); }
    }

    public static nHibernateSessionManager GetInstance()
    {
        // lazy init.
        if (instance == null)
            instance = new nHibernateSessionManager();

        return instance;
    } // GetInstance

    /// <summary>
    /// Initializes the NHibernate session factory upon instantiation.
    /// </summary>
    private nHibernateSessionManager()
    {
        InitSessionFactory();
    }



    /// <summary>
    /// Initializes the NHibernate session factory upon instantiation.
    /// </summary>
    private nHibernateSessionManager(string client)
    {
        InitSessionFactory();
        InitClientSessionFactory(client);
    }

    /// <summary>
    /// Assists with ensuring thread-safe, lazy singleton
    /// </summary>
    private class Nested
    {
        static Nested()
        {
        }

        internal static readonly nHibernateSessionManager nHibernatenHibernateSessionManager = new nHibernateSessionManager();
    }

    #endregion

    private void InitSessionFactory()
    {
        var configuration = new Configuration();
        configuration.Configure(System.Configuration.ConfigurationManager.AppSettings["IDAD_HBM"]);
        configuration.AddAssembly(typeof(enterprise).Assembly);
        idadSessionFactory = configuration.BuildSessionFactory();
    }

    private void InitClientSessionFactory(string client)
    {
        var configuration = new Configuration();
        configuration.Configure(System.Configuration.ConfigurationManager.AppSettings["Client_IDAD_HBM"]);
        configuration.SetProperty("connection.connection_string", client);
        configuration.AddAssembly(typeof(enterprise).Assembly);
        clientSessionFactory = configuration.BuildSessionFactory();
    }

    /// <summary>
    /// Allows you to register an interceptor on a new session.  This may not be called if there is already
    /// an open session attached to the HttpContext.  If you have an interceptor to be used, modify
    /// the HttpModule to call this before calling BeginTransaction().
    /// </summary>
    public void RegisterInterceptor(IInterceptor interceptor)
    {
        ISession session = ThreadSession;

        if (session != null && session.IsOpen)
        {
            throw new CacheException("You cannot register an interceptor once a session has already been opened");
        }

        GetSession(interceptor);
    }

    public ISession GetSession()
    {
        return GetSession(null);
    }

    /// <summary>
    /// Gets a session with or without an interceptor.  This method is not called directly; instead,
    /// it gets invoked from other public methods.
    /// </summary>
    private ISession GetSession(IInterceptor interceptor)
    {
        ISession session = ThreadSession;

        if (session == null)
        {
            if (interceptor != null)
            {
                session = idadSessionFactory.OpenSession(interceptor);
            }
            else
            {
                session = idadSessionFactory.OpenSession();
            }

            ThreadSession = session;
        }

        return session;
    }

    public void CloseSession()
    {
        ISession session = ThreadSession;
        ThreadSession = null;

        if (session != null && session.IsOpen)
        {
            session.Close();
        }
    }

    public void BeginTransaction()
    {
        ITransaction transaction = ThreadTransaction;

        if (transaction == null)
        {
            transaction = GetSession().BeginTransaction();
            ThreadTransaction = transaction;
        }
    }

    public void CommitTransaction()
    {
        ITransaction transaction = ThreadTransaction;

        try
        {
            if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
            {
                transaction.Commit();
                ThreadTransaction = null;
            }
        }
        catch (HibernateException ex)
        {
            RollbackTransaction();
            throw ex;
        }
    }

    public void RollbackTransaction()
    {
        ITransaction transaction = ThreadTransaction;

        try
        {
            ThreadTransaction = null;

            if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
            {
                transaction.Rollback();
            }
        }
        catch (HibernateException ex)
        {
            throw ex;
        }
        finally
        {
            CloseSession();
        }
    }

    /// <summary>
    /// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms 
    /// specific <see cref="CallContext" />.  Discussion concerning this found at 
    /// http://forum.springframework.net/showthread.php?t=572.
    /// </summary>
    private ITransaction ThreadTransaction
    {
        get
        {
            if (IsInWebContext())
            {
                return (ITransaction)HttpContext.Current.Items[TRANSACTION_KEY];
            }
            else
            {
                return (ITransaction)CallContext.GetData(TRANSACTION_KEY);
            }
        }
        set
        {
            if (IsInWebContext())
            {
                HttpContext.Current.Items[TRANSACTION_KEY] = value;
            }
            else
            {
                CallContext.SetData(TRANSACTION_KEY, value);
            }
        }
    }

    /// <summary>
    /// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms 
    /// specific <see cref="CallContext" />.  Discussion concerning this found at 
    /// http://forum.springframework.net/showthread.php?t=572.
    /// </summary>
    private ISession ThreadSession
    {
        get
        {
            if (IsInWebContext())
            {
                return (ISession)HttpContext.Current.Items[SESSION_KEY];
            }
            else
            {
                return (ISession)CallContext.GetData(SESSION_KEY);
            }
        }
        set
        {
            if (IsInWebContext())
            {
                HttpContext.Current.Items[SESSION_KEY] = value;
            }
            else
            {
                CallContext.SetData(SESSION_KEY, value);
            }
        }
    }

    private static bool IsInWebContext()
    {
        return HttpContext.Current != null;
    }

    private const string TRANSACTION_KEY = "CONTEXT_TRANSACTION";
    private const string SESSION_KEY = "CONTEXT_SESSION";

    [Obsolete("only until we can fix the session issue globally")]
    internal ISession OpenSession()
    {
        return idadSessionFactory.OpenSession();
    }
}

}

This is being called from a repository class like so:

public string getByName(string name)
    {
        return getByName(nHibernateSessionManager.Instance.GetSession(), name);
    }

What I would really like to be able to do is the following:

public string getByName(string name, string clientConnectionString)
    {
        return getByName(nHibernateSessionManager.Instance.GetSession(clientConnectionString), name);
    }

But I am having trouble modifying my existing session manager to accomodate this.

+1  A: 

EDIT: For some reason I cannot add this as a comment?

You appear to be asking to swap a connection for a given session. Or rather that is certainly what the code you have written is asking - "return a session identified by the name parameter, and it should also now use the connection string provided by this method."

That is not possible. NHibernate builds a session (and actually really a session factory) per connection and once built the factory and session are immuatble. You cannot change connections for an existing session.

I got the impression that your application involves mostly in initial connection string that is the moving target, but after that your "real" session is on a single database. If that is the case, NHibernate can easily do this. If that is not the case, well, some things NHibernate is just not that well suited for. Maybe understanding a little more about the basis NHibernate operates on is helpful either way?

One of my genuine criticisms of Nhibernate is that you have a somewhat arcane use of terminology and the well known unhelpful nature of it's exception messages. These coupled with the fact that what it is doing is in reality mechanically complicated tends to really obscure that there is a relatively simple and technically sound underlying model.

In this case, if you think about it this business of an immutable session makes a lot of sense. NHibernate connects to a database, but it also maintains objects in the session so they can be persisted back to that database at a later time. NHibernate does not support changing connections per session because there may already be other objects in the session and if you change connections their persistence is no longer assured.

Now, what you can do is create a factory/session per database for multiple databases and access them in one application. But objects still belong to their own session. You can even move objects to a new session with a different connection. In this case you have what would logically be a "replication" scenario. NHibernate supports this but you have to do most of the work. This also makes sense - they really cannot give you that as stable out of the box functionality, you have to manage a process like that on your own.

You can also build code to do exactly what you are asking. But think about what that is. Make a session, not per database, but only for this specific instance of this specific repository. I am thinking that is most likely not really what you want. But that is exactly what the semantics of your request are saying to do. Your existing class, OTOH, was built on different semantics which are more typically what people want - "Build a session for this particular connection definition, i.e this database."

A real need to inject a connection string at the repository level implies that now not only is the database a moving target, but at the actual table level the target also moves. If that is the case, NHibernate is possibly not a good option. If that is not the case, you may be trying to mix programming paradigms. Nhiberate imposes a few what I would call "assumptions" rather than any sort of real "limitations" and in return you don't have to write a bunch of code that would allow you a finer grain of control because often you really don't need that additional control.

Sorry if this is no longer a direct answer to your question, hopefully it is helpful somehow.

Original Answer:

Sure, since the connection info is in the authentication database this is easy.

1) Configure NHibernate in the "usual" fashion and point the config at the authentication database. Get the db connection for the user, and then close that session and session factory. You are done with that one now.

2) Create a new session etc this time in code instead of a config file.

class MyNewSession
 {

     private ISession _session;
     private ISessionFactory _factory;

     public void InitializeSession()
     {

        NHibernate.Cfg.Configuration config = new NHibernate.Cfg.Configuration();
        config.Properties.Clear();

        IDictionary props = new Hashtable();

        // Configure properties as needed, this is pretty minimal standard config here.  
        // Can read in properties from your own xml file or whatever.   

         // Just shown hardcoded here.

        props["proxyfactory.factory_class"] = "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle";
        props["connection.provider"] = "NHibernate.Connection.DriverConnectionProvider";
        props["dialect"] = "NHibernate.Dialect.MsSql2000Dialect";
        props["connection.driver_class"] = "NHibernate.Driver.SqlClientDriver";
        props["connection.connection_string"] = "<YOUR CONNECTION STRING HERE>";
        props["connection.isolation"] = "ReadCommitted";

        foreach (DictionaryEntry de in props)
        {
            config.Properties.Add(de.Key.ToString(), de.Value.ToString());
        }


        // Everything from here on out is the standard NHibernate calls 
        // you already use.

        // Load mappings etc, etc

        // . . . 


        _factory = config.BuildSessionFactory();
        _session = _factory.OpenSession();
    }

}

Sisyphus
I updated my original question to include some code to hopefully illustrate what I am trying to do.
Chris
No, I'm not wanting to change the connection per session, but rather each session/user login, may be associated with a different database. So if user A logs into the application, they may be tied to database A and this would be for the entire session.Then if user B comes along, they would be tied to database B.Everyone shares the same code base, just connect to a different database, but it is always per session/login.
Chris