Управление сессиями NHibernate для мультитенантного приложения ASP.NET - PullRequest
0 голосов
/ 15 октября 2010

У меня есть мультитенантное приложение ASP.NET, в котором все пользователи проходят проверку подлинности с использованием одной базы данных SQL Server. Эта база данных также содержит несколько других типов данных настроек, которые используются в приложении. Каждый клиент после аутентификации использует свою собственную базу данных SQL Server для хранения данных, в целях изоляции. По сути, все клиентские базы данных идентичны и находятся на одном сервере, но также на одном или нескольких серверах.

Приложение в настоящее время написано на платформе asp.net 2.5 и использует Microsoft Practices Enterprise Library для DAL, и мы планируем перейти на 4.0 и внедрить NHibernate вместо MPEL.

Я реализовал решение, уже использующее NHibernate и инфраструктуру 4.0, поэтому я знаком с концепциями. Я нашел ресурсы для моего текущего менеджера сеансов здесь на самом деле. Но у этого приложения была только одна база данных, так что не слишком. Реализация по сути то, что вы видите здесь: http://www.lostechies.com/blogs/nelson_montalvo/archive/2007/03/30/simple-nhibernate-example-part-4-session-management.aspx

Другие решения, которые я видел, предлагают несколько записей конфигурации и / или файлов для управления этим, но это нежелательно, так как мы можем часто добавлять новых клиентов, и вся информация о соединении уже сохраняется в базе данных аутентификации.

Есть ли у кого-нибудь предложения о том, как я могу передать строку подключения клиента менеджеру сеанса?

Ниже приведен мой текущий класс диспетчера сеансов, основанный на упомянутой выше статье.

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)
{
    return getByName(nHibernateSessionManager.Instance.GetSession(), name);
}

Что я действительно хотел бы сделать, это следующее:

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

Но у меня возникают проблемы с изменением моего существующего менеджера сеансов, чтобы приспособиться к этому.

Ответы [ 2 ]

2 голосов
/ 15 октября 2010

Вы, кажется, просите поменять соединение для данного сеанса. Или, скорее, это то, о чем просит написанный вами код - «вернуть сеанс, определенный параметром name, и теперь он также должен использовать строку подключения, предоставленную этим методом».

Это невозможно. NHibernate строит сессию (и фактически фабрику сессий) для каждого соединения, а однажды построенная фабрика и сессия являются неизменяемыми. Вы не можете изменить соединения для существующего сеанса.

У меня сложилось впечатление, что ваше приложение использует в основном исходную строку соединения, которая является движущейся целью, но после этого ваш "настоящий" сеанс находится в одной базе данных. Если это так, NHibernate может легко сделать это. Если это не так, то для некоторых вещей NHibernate просто не подходит. Может быть, понимание того, на чем работает NHibernate, полезно в любом случае?

Одна из моих истинных критики в отношении NHibernate заключается в том, что вы несколько загадочно используете терминологию и хорошо известный бесполезный характер сообщений об исключениях. Это в сочетании с тем фактом, что то, что он делает, на самом деле является механически сложным, действительно скрывает, что существует относительно простая и технически обоснованная базовая модель.

В этом случае, если вы подумаете об этом, этот бизнес неизменного сеанса имеет большой смысл. NHibernate подключается к базе данных, но также поддерживает объекты в сеансе, чтобы они могли быть сохранены в этой базе данных позже. NHibernate не поддерживает изменение соединений в сеансе, поскольку в этом сеансе уже могут быть другие объекты, и если вы меняете соединения, их постоянство больше не гарантируется.

Теперь вы можете создать фабрику / сеанс для каждой базы данных для нескольких баз данных и обращаться к ним в одном приложении, но объекты по-прежнему принадлежат их собственному сеансу. Вы даже можете переместить объекты в новый сеанс с другим подключением. В этом случае у вас есть сценарий «репликации». NHibernate поддерживает это, но вы должны сделать большую часть работы. Это также имеет смысл - они действительно не могут дать вам такую ​​стабильную функциональность, как вы, и вам придется управлять таким процессом самостоятельно.

Вы также можете создать код, чтобы сделать именно то, что вы просите. Но подумай, что это такое. Создайте сеанс не для каждой базы данных, а только для этого конкретного экземпляра этого конкретного хранилища. Я думаю, что это, скорее всего, не совсем то, что вы хотите. Но это именно то, что семантика вашего запроса говорит сделать. С другой стороны, ваш существующий класс был построен на другой семантике, которая, как правило, более того нужна людям: «Создайте сеанс для этого конкретного определения соединения , т.е. этой базы данных».

Реальная потребность ввести строку соединения на уровне хранилища подразумевает, что теперь не только база данных является движущейся целью, но и на уровне фактической таблицы цель также перемещается. Если это так, то NHibernate, возможно, не очень хороший вариант. Если это не так, возможно, вы пытаетесь смешать парадигмы программирования. NHiberate навязывает несколько того, что я бы назвал «допущениями», а не какими-то реальными «ограничениями», и взамен вам не нужно писать кучу кода, который позволил бы вам получить более тонкую часть контроля, потому что часто вы действительно этого не делаете нужен этот дополнительный контроль.

Извините, если это больше не прямой ответ на ваш вопрос, надеюсь, это как-то полезно.


Оригинальный ответ:

Конечно, поскольку информация о соединении находится в базе данных аутентификации, это легко.

1) Сконфигурируйте NHibernate «обычным» способом и укажите конфигурацию в базе данных аутентификации. Получите соединение БД для пользователя, а затем закройте этот сеанс и фабрику сеансов. Вы закончили с этим сейчас.

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

}
0 голосов
/ 15 декабря 2012

Я знаю, что это старое, но если вы не нашли решение, я надеюсь, что это поможет, я создал решение, которое использует мультисессионный завод с использованием unhaddins (я внес изменения в соответствии с моими потребностями).По сути, мультисессионная фабрика создает фабрики сеансов для каждой базы данных и сохраняет их в объекте приложения.

В зависимости от клиента вызов getfactory («имя фабрики из файла конфигурации фабрики») возвращает правильную базу данных для запроса.

Вам придется изменить свой модуль управления, чтобы поддержать это и все ваши репозитории, чтобы поддержать изменение в управлении.Поначалу это может быть нецелесообразно, но вы все равно должны их изменить.Ваши вызовы из вашего репозитория могут быть примерно такими:

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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...