管理多租户ASP.NET应用程序的NHibernate会话

管理多租户ASP.NET应用程序的NHibernate会话,asp.net,nhibernate,session,Asp.net,Nhibernate,Session,我有一个现有的多租户ASP.NET应用程序,其中所有用户都针对单个SQL Server数据库进行身份验证。此数据库还包含应用程序中使用的几个其他设置类型数据。每个客户机在经过身份验证后,都利用自己的SQL Server数据库进行数据存储,以达到隔离的目的。实际上,所有客户机数据库都是相同的,并且驻留在同一台服务器上,但也驻留在一台或多台服务器上 该应用程序目前使用asp.net 2.5框架编写,并利用Microsoft Practices Enterprise Library for DAL,我

我有一个现有的多租户ASP.NET应用程序,其中所有用户都针对单个SQL Server数据库进行身份验证。此数据库还包含应用程序中使用的几个其他设置类型数据。每个客户机在经过身份验证后,都利用自己的SQL Server数据库进行数据存储,以达到隔离的目的。实际上,所有客户机数据库都是相同的,并且驻留在同一台服务器上,但也驻留在一台或多台服务器上

该应用程序目前使用asp.net 2.5框架编写,并利用Microsoft Practices Enterprise Library for DAL,我们希望迁移到4.0并实现NHibernate以取代MPEL

我已经使用NHibernate和4.0框架实现了一个解决方案,因此我熟悉这些概念。事实上,我在这里找到了当前会话管理器的资源。但该应用程序只有一个数据库,所以没有太多数据库。实现基本上就是您在这里看到的:

我看到的其他解决方案建议使用多个配置条目和/或文件来管理这一点,但这并不可取,因为我们可能会频繁添加新客户端,并且所有连接信息都已保存在身份验证数据库中

对于如何将客户端的连接字符串传递给会话管理器,有人有什么建议吗

下面是我当前的会话管理器类,它基于上述文章

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
{
    /// <summary>
    /// 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.
    /// </summary>
    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();
        }
    }
}
我真正希望能够做到的是:

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

但我在修改现有会话管理器以适应此情况时遇到问题。

您似乎要求为给定会话交换连接。或者更确切地说,这就是您编写的代码所要求的——“返回一个由name参数标识的会话,它现在还应该使用此方法提供的连接字符串。”

这是不可能的。NHibernate为每个连接构建一个会话(实际上是一个会话工厂),一旦构建,工厂和会话是不可变的。无法更改现有会话的连接

我得到的印象是,您的应用程序主要涉及初始连接字符串,这是移动的目标,但在此之后,您的“真正”会话位于单个数据库上。如果是这样,NHibernate可以很容易地做到这一点。如果不是这样的话,那么,有些事情NHibernate就不太适合。也许对NHibernate的运作基础有更多的了解会有所帮助

我对NHibernate的一个真正批评是,您对术语的使用有些神秘,并且它的异常消息的众所周知的无用性质。再加上它所做的实际上是机械上复杂的,这一事实往往让人难以理解,有一个相对简单、技术上可靠的基础模型

在这种情况下,如果您仔细想想,这个不可变会话的业务非常有意义。NHibernate连接到数据库,但它也在会话中维护对象,以便以后可以将它们持久化回该数据库。NHibernate不支持更改每个会话的连接,因为会话中可能已经有其他对象,如果更改连接,则不再保证它们的持久性

现在,您可以为多个数据库创建每个数据库的工厂/会话,并在一个应用程序中访问它们,但对象仍然属于它们自己的会话。您甚至可以将对象移动到具有不同连接的新会话。在本例中,逻辑上是“复制”场景。NHibernate支持这一点,但您必须完成大部分工作。这也是有道理的-他们真的不能给你稳定的开箱即用的功能,你必须自己管理这样的过程

您还可以构建代码来完全按照您的要求执行。但是想想那是什么。创建会话,不是针对每个数据库,而是仅针对此特定存储库的此特定实例。我认为这很可能不是你真正想要的。但这正是您请求的语义所说的。另一方面,您现有的类是基于不同的语义构建的,这些语义更符合人们的需求——“为这个特定的连接定义构建会话,即这个数据库。”

真正需要在存储库级别注入连接字符串意味着,现在不仅数据库是一个移动的目标,而且在实际的表级别,目标也在移动。如果是这样的话,NHibernate可能不是一个好的选择。如果不是这样,您可能会尝试混合编程范例。NHiberate强加了一些我称之为“假设”的东西,而不是任何一种真正的“限制”,作为回报,您不必编写一堆代码来实现更细粒度的控制,因为通常您确实不需要额外的控制

抱歉,如果这不再是对你问题的直接回答,希望它能有所帮助


原始答复:

当然,因为连接信息在身份验证数据库中,所以这很容易

1) 以“常规”方式配置NHibernate,并将配置指向身份验证数据库。获取用户的db连接,然后关闭该会话和会话工厂。你现在已经做完了

2) 这次用代码而不是配置文件创建新会话等

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();
    }

}
类MyNewSession
{
非公开会议;
私营ISessionFactory(工厂);;
公共无效初始化会话()
{
NHibernate.Cfg.Configuration config=新的NHibernate.Cfg.Configuration();
config.Properties.Clear();
IDictionary props=新哈希表();
//根据需要配置属性,这是非常简单的标准配置
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();
    }

}
public string getByName(string name)
{
    return getByName(nHibernateSessionManager.SessionFactoryManager.GetFactory(Session["session variable that holds client session factory name that was set on login"]).GetCurrentSession(), name);
}
public string getByName(string name)
{
    return getByName(nHibernateSessionManager.GetSession(Session["session variable that holds client session factory name that was set on login"]), name);
}