Click here to Skip to main content
Email Password   helpLost your password?

Introduction

A previous CodeProject article described using NHibernate with ASP.NET; it offered guidelines for communicating with a single database. But, it is sometimes necessary to communicate with multiple databases concurrently. For NHibernate to do this, a session factory needs to exist for each database that you will be communicating with. But, as is often the case with multiple databases, some of the databases are rarely used. So, it may be a good idea to not create session factories until they're actually needed. This article picks up where the previous NHibernate with ASP.NET article left off, and describes the implementation details of this simple-sounding approach. Although the previous article focused on ASP.NET, the below suggestion is supported in both ASP.NET and Windows Forms.

A thank you goes out to Larry Silverman whose collaboration assisted in the development of this approach.

Multiple Database Configuration

The first thing to do when working with multiple databases is to configure proper communications. Create a separate config file for each database, put them all into a central config folder, and then reference them from the web/app.config.

Sample DB Config File

As an example, the following config file could be named ~/config/MyProjectDb.config.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration  xmlns="urn:nhibernate-configuration-2.0" >
    <session-factory name="myProjectDb">
        <property name="hibernate.connection.provider">
          NHibernate.Connection.DriverConnectionProvider</property>
        <property name="hibernate.dialect">
          NHibernate.Dialect.MsSql2000Dialect</property>
        <property name="hibernate.connection.driver_class">
          NHibernate.Driver.SqlClientDriver</property>
        <property name="hibernate.connection.connection_string">
            Data Source=MySqlServer;Database=MyDb;
            User ID=username;Password=password;</property>
        <property name="hibernate.connection.isolation">
           ReadCommitted</property>
        <property name="hibernate.default_schema">MyDb.dbo</property>
        <!-- Assembly containing the embedded HBM mapping files -->
        <mapping assembly="MyProject.Core" />
    </session-factory>
</hibernate-configuration>

Web.config / App.config Settings

Now that the database configuration has been defined in one or more config files, the NHibernate session manager needs to be informed about which session factories are available for use and where their respective config files reside. A solution that will provide a great amount of reuse is to create a custom config file reader. The web/app.config then states where each config file lives, as follows:

<?xml version="1.0"?>
<configuration>
    <configSections>
        <section name="nhibernateSettings"
           type="SomeReusableProject.OpenSessionInViewSection, 
                 SomeReusableProject" />
    </configSections>
    <nhibernateSettings>
        <!-- List every session factory that will be needed; 
             transaction management and closing sessions 
        will be managed with an open-session-in-view HTTP module -->
        <sessionFactories>
            <clearFactories />
            <sessionFactory name="myProjectDb" 
                factoryConfigPath="C:\MyProject.Web\Config\MyProjectDb.config" 
                isTransactional="true" />
            <sessionFactory name="otherDb" 
                factoryConfigPath="C:\MyProject.Web\Config\JdeNHibernate.config" />
        </sessionFactories>
    </nhibernateSettings>
...

The above configuration is not an "out of the box" NHibernate settings section. Three classes must be written to parse this custom configuration information: OpenSessionInViewSection will be the overall handler for parsing the config file, SessionFactoriesCollection will serve as the container for the listing of "sessionFactory" elements, and SessionFactoryElement will map the individual session factory settings.

OpenSessionInViewSection

namespace SomeReusableProject
{
    /// <summary>
    /// Encapsulates a section of Web/App.config
    /// to declare which session factories are to be created.
    /// Kudos go out to 
    /// http://msdn2.microsoft.com/en-us/library/
    ///    system.configuration.configurationcollectionattribute.aspx
    /// for this technique - it was by far the best overview of the subject.
    /// </summary>

    public class OpenSessionInViewSection : ConfigurationSection
    {
        [ConfigurationProperty("sessionFactories", IsDefaultCollection = false)]
        [ConfigurationCollection(typeof(SessionFactoriesCollection), 
            AddItemName="sessionFactory", 
            ClearItemsName="clearFactories")]
        public SessionFactoriesCollection SessionFactories {
            get {
                SessionFactoriesCollection sessionFactoriesCollection =
                    (SessionFactoriesCollection) base["sessionFactories"];
                return sessionFactoriesCollection;
            }
        }
    }
}

SessionFactoriesCollection

namespace SomeReusableProject
{
    [ConfigurationCollection(typeof(SessionFactoryElement))]
    public sealed class SessionFactoriesCollection : 
                        ConfigurationElementCollection
    {
        public SessionFactoriesCollection() {
            SessionFactoryElement sessionFactory = 
               (SessionFactoryElement) CreateNewElement();
            Add(sessionFactory);
        }

        public override ConfigurationElementCollectionType CollectionType {
            get {
                return ConfigurationElementCollectionType.AddRemoveClearMap;
            }
        }

        protected override ConfigurationElement CreateNewElement() {
            return new SessionFactoryElement();
        }

        protected override object GetElementKey(ConfigurationElement element) {
            return ((SessionFactoryElement)element).Name;
        }
        
        public SessionFactoryElement this[int index] {
            get {
                return (SessionFactoryElement) BaseGet(index);
            }
            set {
                if (BaseGet(index) != null) {
                    BaseRemoveAt(index);
                }

                BaseAdd(index, value);
            }
        }
        
        new public SessionFactoryElement this[string name] {
            get {
                return (SessionFactoryElement) BaseGet(name);
            }
        }
        
        public int IndexOf(SessionFactoryElement sessionFactory) {
            return BaseIndexOf(sessionFactory);
        }
        
        public void Add(SessionFactoryElement sessionFactory) {
            BaseAdd(sessionFactory);
        }

        protected override void BaseAdd(ConfigurationElement element) {
            BaseAdd(element, false);
        }
        
        public void Remove(SessionFactoryElement sessionFactory) {
            if (BaseIndexOf(sessionFactory) >= 0) {
                BaseRemove(sessionFactory.Name);
            }
        }
        
        public void RemoveAt(int index) {
            BaseRemoveAt(index);
        }
        
        public void Remove(string name) {
            BaseRemove(name);
        }
        
        public void Clear() {
            BaseClear();
        }
    }
}

SessionFactoryElement

Note that the configuration element allows an optional setting of "isTransactional" to be provided. By default, this setting is false. If it's set to true, then all calls to a DB during a single HTTP request will be contained within a single transaction for the given session factory. To illustrate this, assume two DBs exist: MyDb and OtherDb. The web/app.config setting for MyDb declares "isTransactional" to be true. Furthermore, assume no transactional setting is provided for OtherDb; i.e., "isTransactional" remains false. As will be described later, since MyDb is to be transactional, a transaction will be begun at the start of each HTTP request, thus encapsulating every call to MyDb for the life of the HTTP request. At the end of the HTTP request, the single transaction for calls to MyDb will be committed. On the flipside, since OtherDb won't be transactional, any failed call to OtherDb will not cause a rollback of any other call to OtherDb during the life of each HTTP request.

using Configuration_StringValidator=System.Configuration.StringValidator;

namespace SomeReusableProject
{
    public class SessionFactoryElement : ConfigurationElement
    {
        public SessionFactoryElement() {}
        
        public SessionFactoryElement(string name, string configPath) {
            Name = name;
            FactoryConfigPath = configPath;
        }
        
        [ConfigurationProperty("name", IsRequired = true, 
             IsKey=true, DefaultValue="Not Supplied")]
        public string Name {
            get { return (string) this["name"]; }
            set { this["name"] = value; }
        }

        [ConfigurationProperty("factoryConfigPath", IsRequired = true, 
                 DefaultValue = "Not Supplied")]
        public string FactoryConfigPath {
            get { return (string)this["factoryConfigPath"]; }
            set { this["factoryConfigPath"] = value; }
        }

        [ConfigurationProperty("isTransactional", 
                      IsRequired = false, DefaultValue = false)]
        public bool IsTransactional {
            get { return (bool)this["isTransactional"]; }
            set { this["isTransactional"] = value; }
        }
    }
}

Open-Session-in-View Revisited

Now that the configuration files and config readers are in place, an HTTP module can be enlisted to read the configuration settings and begin transactions, accordingly. (Obviously, this HTTP module is ASP.NET specific, and does not apply to .NET WinForms - but the underlying concepts are the same.) In the previous NHibernate with ASP.NET article, it was assumed that Open-Session-in-View would always utilize a transaction for the life of the HTTP request. The modified NHibernateSessionModule.cs below begins a transaction only when "isTransactional" is set to true in the web.config file. As stated previously, this HTTP module only begins one transaction per HTTP request per session factory when isTransactional="true" for the given session factory. Lastly, each transaction is then committed at the end of the HTTP request.

One other interesting item to note in the code is that it passes the FactoryConfigPath, pulled from web.config, to the NHibernateSessionManager, for two purposes:

  1. NHibernateSessionManager will use this path as a key in a hashtable to look for an already created session factory, and
  2. NHibernateSessionManager will use the related config file to initialize a new session factory, if one was not found.

NHibernateSessionModule

namespace SomeReusableProject
{
    /// <summary>
    /// Implements the Open-Session-In-View pattern
    /// using <see cref="NHibernateSessionManager" />.
    /// Inspiration for this class came from Ed Courtenay at 
    /// http://sourceforge.net/forum/message.php?msg_id=2847509.
    /// </summary>

    public class NHibernateSessionModule : IHttpModule
    {
        public void Init(HttpApplication context) {
            context.BeginRequest += new EventHandler(BeginTransaction);
            context.EndRequest += new EventHandler(CommitAndCloseSession);
        }

        public void Dispose() { }

        /// <summary>
        /// Opens a session within a transaction
        /// at the beginning of the HTTP request. Note that 
        /// it ONLY begins transactions for those designated as being transactional.
        /// </summary>

        private void BeginTransaction(object sender, EventArgs e) {
            OpenSessionInViewSection openSessionInViewSection = 
                                       GetOpenSessionInViewSection();

            foreach (SessionFactoryElement sessionFactorySettings in 
                         openSessionInViewSection.SessionFactories) {
                if (sessionFactorySettings.IsTransactional) {
                    NHibernateSessionManager.Instance.BeginTransactionOn(
                                sessionFactorySettings.FactoryConfigPath);
                }
            }
        }

        /// <summary>
        /// Commits and closes the NHibernate session provided
        /// by the supplied <see cref="NHibernateSessionManager"/>.
        /// Assumes a transaction was begun at the beginning
        /// of the request; but a transaction or session does
        /// not *have* to be opened for this to operate successfully.
        /// </summary>

        private void CommitAndCloseSession(object sender, EventArgs e) {
            OpenSessionInViewSection openSessionInViewSection = 
                                               GetOpenSessionInViewSection();
            
            try {
                // Commit every session factory that's holding a transactional session

                foreach (SessionFactoryElement sessionFactorySettings in 
                         openSessionInViewSection.SessionFactories) {
                    if (sessionFactorySettings.IsTransactional) {
                        NHibernateSessionManager.Instance.CommitTransactionOn(
                               sessionFactorySettings.FactoryConfigPath);
                    }
                }
            }
            finally {
                // No matter what happens,
                // make sure all the sessions get closed

                foreach (SessionFactoryElement sessionFactorySettings in 
                         openSessionInViewSection.SessionFactories) {
                    NHibernateSessionManager.Instance.CloseSessionOn(
                             sessionFactorySettings.FactoryConfigPath);
                }
            }
        }

        private OpenSessionInViewSection GetOpenSessionInViewSection() {
            OpenSessionInViewSection openSessionInViewSection = ConfigurationManager
                .GetSection("nhibernateSettings") as OpenSessionInViewSection;

            if (openSessionInViewSection == null)
        throw new ConfigurationErrorsException("The nhibernateSettings " + 
                  "section was not found by ConfigurationManager.");

            return openSessionInViewSection;
        }
    }
}

Managing Multiple Session Factories

A remaining task is to modify the NHibernateSessionManager class from the original NHibernate with ASP.NET article so that it now manages multiple session factories. The general idea is that each session factory is stored in a hashtable using its associated config file path as the key. As described previously, this makes it easy to locate the session factory, or create a new one, if not found.

Note that even though the following class uses HttpRuntime.Cache for storing and retrieving active session factories, it will still work in a .NET WinForms environment.

NHibernateSessionManager

namespace SomeReusableProject
{
    /// <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
    {
        #region Thread-safe, lazy Singleton

        /// <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 Nested.NHibernateSessionManager;
            }
        }

        /// <summary>
        /// Private constructor to enforce singleton
        /// </summary>

        private NHibernateSessionManager() {}

        /// <summary>
        /// Assists with ensuring thread-safe, lazy singleton
        /// </summary>

        private class Nested
        {
            static Nested() { }
            internal static readonly NHibernateSessionManager 
               NHibernateSessionManager = new NHibernateSessionManager();
        }

        #endregion

        /// <summary>
        /// This method attempts to find a session factory
        /// in the <see cref="HttpRuntime.Cache" /> 
        /// via its config file path; if it can't be
        /// found it creates a new session factory and adds
        /// it the cache. Note that even though this uses HttpRuntime.Cache,
        /// it should still work in Windows applications; see
        /// http://www.codeproject.com/csharp/cacheinwinformapps.asp
        /// for an examination of this.
        /// </summary>
        /// <param name="sessionFactoryConfigPath">Path location
        /// of the factory config</param>

        private ISessionFactory GetSessionFactoryFor(string sessionFactoryConfigPath) {
            if (string.IsNullOrEmpty(sessionFactoryConfigPath))
                throw new ArgumentNullException("sessionFactoryConfigPath" + 
                          " may not be null nor empty");
            
            //  Attempt to retrieve a cached SessionFactory from the HttpRuntime's cache.
            ISessionFactory sessionFactory = 
              (ISessionFactory)HttpRuntime.Cache.Get(sessionFactoryConfigPath);

            //  Failed to find a cached SessionFactory so make a new one.
            if (sessionFactory == null) {
                if (! File.Exists(sessionFactoryConfigPath))
                    // It would be more appropriate to throw
                    // a more specific exception than ApplicationException

                    throw new ApplicationException(
                        "The config file at '" + sessionFactoryConfigPath + 
                        "' could not be found");
                
                NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();
                cfg.Configure(sessionFactoryConfigPath);

                //  Now that we have our Configuration object, create a new SessionFactory

                sessionFactory = cfg.BuildSessionFactory();

                if (sessionFactory == null) {
                    throw new InvalidOperationException(
                      "cfg.BuildSessionFactory() returned null.");
                }

                HttpRuntime.Cache.Add(sessionFactoryConfigPath, 
                            sessionFactory, null, DateTime.Now.AddDays(7),
                    TimeSpan.Zero, CacheItemPriority.High, null);
            }

            return sessionFactory;
        }

        public void RegisterInterceptorOn(string sessionFactoryConfigPath, 
                                          IInterceptor interceptor) {
            ISession session = (ISession)contextSessions[sessionFactoryConfigPath];

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

            GetSessionFrom(sessionFactoryConfigPath, interceptor);
        }

        public ISession GetSessionFrom(string sessionFactoryConfigPath) {
            return GetSessionFrom(sessionFactoryConfigPath, null);
        }

        private ISession GetSessionFrom(string sessionFactoryConfigPath, 
                                        IInterceptor interceptor) {
            ISession session = (ISession)contextSessions[sessionFactoryConfigPath];

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

                contextSessions[sessionFactoryConfigPath] = session;
            }
            
            if (session == null) 
                // It would be more appropriate to throw
                // a more specific exception than ApplicationException

                throw new ApplicationException("session was null");
            
            return session;
        }

        public void CloseSessionOn(string sessionFactoryConfigPath) {
            ISession session = (ISession)contextSessions[sessionFactoryConfigPath];
            contextSessions.Remove(sessionFactoryConfigPath);

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

        public void BeginTransactionOn(string sessionFactoryConfigPath) {
            ITransaction transaction = 
              (ITransaction)contextTransactions[sessionFactoryConfigPath];

            if (transaction == null) {
                transaction = GetSessionFrom(sessionFactoryConfigPath).BeginTransaction();
                contextTransactions.Add(sessionFactoryConfigPath, transaction);
            }
        }

        public void CommitTransactionOn(string sessionFactoryConfigPath) {
            ITransaction transaction = 
              (ITransaction)contextTransactions[sessionFactoryConfigPath];

            try {
                if (transaction != null && !transaction.WasCommitted 
                                        && !transaction.WasRolledBack) {
                    transaction.Commit();
                    contextTransactions.Remove(sessionFactoryConfigPath);
                }
            }
            catch (HibernateException) {
                RollbackTransactionOn(sessionFactoryConfigPath);
                throw;
            }
        }

        public void RollbackTransactionOn(string sessionFactoryConfigPath) {
            ITransaction transaction = 
              (ITransaction) contextTransactions[sessionFactoryConfigPath];

            try {
                contextTransactions.Remove(sessionFactoryConfigPath);

                if (transaction != null && !transaction.WasCommitted 
                                        && !transaction.WasRolledBack) {
                    transaction.Rollback();
                }
            }
            finally {
                CloseSessionOn(sessionFactoryConfigPath);
            }
        }

        /// <summary>
        /// Since multiple databases may be in use, there may be one transaction per database 
        /// persisted at any one time. The easiest way to store them is via a hashtable
        /// with the key being tied to session factory.
        /// </summary>

        private Hashtable contextTransactions {
            get {
                if (CallContext.GetData("CONTEXT_TRANSACTIONS") == null) {
                    CallContext.SetData("CONTEXT_TRANSACTIONS", new Hashtable());
                }

                return (Hashtable)CallContext.GetData("CONTEXT_TRANSACTIONS");
            }
        }

        /// <summary>
        /// Since multiple databases may be in use, there may be one session per database 
        /// persisted at any one time. The easiest way to store them is via a hashtable
        /// with the key being tied to session factory.
        /// </summary>

        private Hashtable contextSessions {
            get {
                if (CallContext.GetData("CONTEXT_SESSIONS") == null) {
                    CallContext.SetData("CONTEXT_SESSIONS", new Hashtable());
                }
                
                return (Hashtable)CallContext.GetData("CONTEXT_SESSIONS");
            }
        }
    }
}

Using NHibernateSessionManager

Finally, the only other modification to the original NHibernate with ASP.NET article is to retrofit GenericNHibernateDao.cs to pass the session factory's config file path to the NHibernateSessionManager class for determining which session factory to use.

namespace SomeReusableProject
{
    public abstract class GenericNHibernateDAO<T, ID> : IGenericDAO<T, ID>
    {
        /// <param name="sessionFactoryConfigPath">Fully qualified
        /// path of the session factory's config file</param>

        public GenericNHibernateDAO(string sessionFactoryConfigPath) {
           if (string.IsNullOrEmpty(sessionFactoryConfigPath))
                throw new ArgumentNullException("sessionFactoryConfigPath " + 
                          "may not be null nor empty");

            SessionFactoryConfigPath = sessionFactoryConfigPath;
        }
        
        /// <summary>
        /// Exposes the ISession used within the DAO.
        /// </summary>

        private ISession session {
            get {
                return NHibernateSessionManager.Instance.GetSessionFrom(
                       SessionFactoryConfigPath);
            }
        }

        /// <summary>
        /// Loads an instance of type T from the DB based on its ID.
        /// </summary>

        public T GetById(ID id, bool shouldLock) {
            T entity;

            if (shouldLock) {
                entity = (T) session.Load(persitentType, id, LockMode.Upgrade);
            }
            else {
                entity = (T)session.Load(persitentType, id);
            }

            return entity;
        }

        protected readonly string SessionFactoryConfigPath;

        ...

Any concrete DAO that inherits from GenericNHibernateDao must then pass the session factory config path to the parent class' constructor; e.g., public UserDao(string sessionFactoryConfigPath) : base(sessionFactoryConfigPath) { }.

Summary

Communicating with multiple databases concurrently doesn't come up often, but when it does, NHibernate is still a fitting solution. The solution discussed above is only one possible approach, but provides a set of reusable classes for communicating with any number of databases, with easy to maintain config files.

As always, your experiences, feedback, and suggestions are most welcome.

Additional Resources

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralIs the somebody can provide a sample of project which uses 2 databases
midavik
8:03 23 Nov '09  
Is the somebody can provide a sample of project which uses 2 databases?
QuestionAssemblies could not be found
Tomas CA
6:33 11 Aug '08  
I created a separate project/assembly for this set of classes, except for the DAOs.
I referenced this external project/assembly from the project/assembly where I have my model.
Config files are being loaded fine until <mapping assembly="..." /> is reached.
I am so sorry to bother anybody with something that should be easy to solve, but I don't know how to fully qualify the Assembly holding my model so that it can be reached. Any help? Thanks in advance.
AnswerRe: Assemblies could not be found
Billy McCafferty
7:49 11 Aug '08  
Hi, no problem. The assembly mappint should simply be the assebly which contents the HBMs. So if your HBMs are in MyProject.Core, then it should <mapping assembly="MyProject.Core" />. If your HBMs are in a project just called MyProject, then it should be &lt;mapping assembly="MyProject" /&gt;. Give it a try and let me know if that works for you.

Billy
QuestionI must be missing something [modified]
leaf93
11:10 5 Jun '08  
Why do you need to pass the SessionFactoryConfigPath in the GenericDao if you put this path in the web.config?

return NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath);


Shouldn't the sessionFactoryConfigPath be read from the web.config.

modified on Friday, June 6, 2008 1:47 PM

AnswerRe: I must be missing something
Billy McCafferty
14:32 12 Jun '08  
You're right, that would be simpler.
QuestionExceptions being thrown when mapping 2 objects together from different DB's [modified]
driskel
7:41 25 Apr '08  
I have a test project trying to use the framework provided in the NHibernate Best Practices 1.2 article. This project is attempting to have a domain model where persistent classes are coming from both databases and are part of the same namespace.   The goal is to not care where any of the data lives but to just build a rich domain layer.

If I create an object from each database on there own there is not an issue and the objects are hydrated properly from each database.   If one object references the other then an exception is thrown on the referenced object from the other DB.   Enclosed are the exceptions being thrown.

I get this error on all properties of the referenced class.

Exception:

base {NHibernate.HibernateException} = {"could not load an entity: [Model.Production#2][SQL: SELECT production0_.ROWID as ROWID1_1_0_, production0_.ACTIVATION_KEY as

ACTIVATION2_1_0_ FROM Production production0_ WHERE production0_.ROWID=?]"}

Most Inner Exception:

"Invalid object name 'Production'."

Here is an example of the calling code.   This code is for demonstration purposes only.

<code>
SupportRepository supportRepository = new SupportRepository(SUPPORTCONFIGFILEPATH);

//This is the collection of objects that is in error.
IList<Support> supports = supportRepository.Get();

IProductionRepository = new ProductionRepository(PRODUCTIONCONFIGFILEPATH)
IList<Production> productions = ProductionFactory.Get();
</code>

// using the debugger supports[0].Production.ActivationKey is where the exception is visible.   Does anybody have any suggestions to solve this problem?

// Code for reference:

Support Database Config File:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration   xmlns="urn:nhibernate-configuration-2.2" >
     <session-factory name="Support">
          <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
          <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
          <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
          <property name="connection.connection_string">Data Source=localhost;Initial Catalog=Support;Uid=USER;pwd=PWD;</property>
          <property name="connection.isolation">ReadCommitted</property>
          <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
          <mapping assembly="Model" />
     </session-factory>
</hibernate-configuration>

Production Database Config File

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration   xmlns="urn:nhibernate-configuration-2.2" >
     <session-factory name="Production">
          <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
          <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
          <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
          <property name="connection.connection_string">Data Source=localhost;Initial Catalog=Production;Uid=USER;pwd=PWD;</property>
          <property name="connection.isolation">ReadCommitted</property>
          <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
          <mapping assembly="Model" />
     </session-factory>
</hibernate-configuration>

Config File (webapp.config or app.config)

<NHibernateSettings>
     <sessionFactories>
          <clearFactories />
          <sessionFactory name="Support" factoryConfig="C:Support.config" isTransactional="true"/>
          <sessionFactory name="Production" factoryConfig="C:\Production.config" isTransactional="true" />
     </sessionFactories>
</NHibernateSettings>

Class is a persistent class in the support database with a reference to an object from the production database.

<code>
namespace Model
{
     [Serializable()]    
     public class Support
     {
          private Guid _id = Guid.Empty;
          private String _name = String.Empty;
          private Production _production;

          public virtual Guid ID
          {
               get { return _id; }
               set { _id = value; }
          }

          public virtual string Name
          {
               get { return _name; }
               set { _name = value; }
          }

          // Class from other DB
          public virtual Production Production
          {
               get { return _production; }
               set { _production = value; }
          }
     }
}
</code>

NHibernate Mapping :

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Model" assembly="Model" >
     <class name="Support" table="TestTable">
          <id name="ID">
               <generator class="guid.comb" />
          </id>
          <property name="Name" />
          <many-to-one name="Production"   class="Production" access="field.camelcase-underscore" column="ProductionId"></many-to-one>
     </class>
</hibernate-mapping>

Class is a persistent class in the production DB.

<code>
namespace Model
{
     [Serializable()]
     public class Production
     {
          private int _id;
          private String _activationKey = string.Empty;

          public virtual int Id
          {
               get { return _id; }
               set { _id = value; }
          }

          public virtual string ActivationKey
          {
               get { return _activationKey; }
               set { _activationKey = value; }
          }
     }
}
</code>

NHibernate Mapping File:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Model" assembly="Model">
     <class name="Production" table="Production">
          <id name="Id" column="ID" type="Int32">
               <generator class="native" />
          </id>
          <property name="ActivationKey" column="ACTIVATION_KEY" />
     </class>
</hibernate-mapping>

Thank you.

Michael

<div class="ForumMod">modified on Friday, April 25, 2008 12:47 PM</div>
GeneralRe: Exceptions being thrown when mapping 2 objects together from different DB's
Billy McCafferty
7:54 25 Apr '08  
Hi Michael,

The way I've addressed this situation is to have a factory object which loads and combines the objects together. So the factory would load Support and then it would load Production, and then it would attach Production to the Support object and Support's mapping would exclude any references to Production. Since there haven't been many times that I've needed this, I haven't pursued the lazy loading from one DB to another. Do you feel this might be an adequate solution for you as well? It forces you to make some degree of DB faciliation in your domain, but not too much if it's an isolated case.

You may also want to try the NHibernate forums to see if others have had luck with this.

Billy
QuestionRe: Exceptions being thrown when mapping 2 objects together from different DB's
driskel
8:02 25 Apr '08  
Thanks Billy for the rather quick response and the fantastic article.

I was trying to avoid any DB work at all in the domain if possible (POCO classes). Do you have any reference code to share?

Is there a way in the current framework to use one Session to connect to multiple DB's with one Domain assembly?
GeneralRe: Exceptions being thrown when mapping 2 objects together from different DB's
Billy McCafferty
8:09 25 Apr '08  
As long as the DBs are on the same SQL server, then you should be able to change the DB name for each mapping file since they can share the same session factory. (I haven't actually tried this, but that's my understanding.) If they're on separate servers, then they'll need separate session factories.

With respect to the factory, you'll still be able to work with POCO's; simply pass IObject1Dao and IObject2Dao to the factory's constructor. In this way, you can unit test the factory with mocked DAOs and inject its DAO dependencies from your controller/presenter/service-layer/code-behind.

Billy
GeneralRe: Exceptions being thrown when mapping 2 objects together from different DB's
driskel
8:28 25 Apr '08  
Thanks for everything Billy. I found an article by hectorjcruz and I will try it out over the weekend.
GeneralRe: Exceptions being thrown when mapping 2 objects together from different DB's
Billy McCafferty
8:33 25 Apr '08  
Could you list the URL for that article here?

Thanks!
Billy
GeneralRe: Exceptions being thrown when mapping 2 objects together from different DB's : Url Posted
driskel
8:45 25 Apr '08  
As requested. It looks like you have responded to this discussion in the past.

http://forum.hibernate.org/viewtopic.php?t=962161

Thanks again for all your help.
AnswerRe: Exceptions being thrown when mapping 2 objects together from different DB's [modified]
driskel
11:07 29 Apr '08  
Billy, It looks at least from a cursory test that it works.   Granted I have done nothing other than retrieve a simple object, but it is success none the less.

What I have done is use one session verses 2 for the databases and used the schema property on the mappings and remove the Database from the connection_string.

Thanks for all the help.

<NHibernateSettings>
          <sessionFactories>
               <clearFactories />
               <sessionFactory name="Support" factoryConfig="C:\Support.config" isTransactional="true"/>
          </sessionFactories>
</NHibernateSettings>

Support.config for repositories:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration   xmlns="urn:nhibernate-configuration-2.2" >
     <session-factory name="Load.Support">
          <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
          <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
          <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
          <property name="connection.connection_string">Data Source=.\SQL2005;Uid=USER;pwd=PWD!;</property>
          <property name="connection.isolation">ReadCommitted</property>
          <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
          <!-- HBM Mapping Files -->
          <mapping assembly="Model" />
     </session-factory>
</hibernate-configuration>

Mapping for Class coming Production Database

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Model" assembly="Model" schema="Production.dbo" >
     <class name="Production" table="Production">
          <id name="RowId" column="ROWID" type="Int32">
               <generator class="native" />
          </id>
          <property name="ActivationKey" column="ACTIVATION_KEY" />
     </class>
</hibernate-mapping>

Mapping for class coming from Support Database.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Model" assembly="Model" schema="Support.dbo" >
     <class name="Support" table="Support">
          <id name="ID">
               <generator class="guid.comb" />
          </id>
          <property name="Name" />
          <many-to-one name="Production"   class="Production"   column="ProductionId"></many-to-one>
     </class>
</hibernate-mapping>

<div class="ForumMod">modified on Tuesday, April 29, 2008 4:25 PM</div>
GeneralRe: Exceptions being thrown when mapping 2 objects together from different DB's
Billy McCafferty
12:22 29 Apr '08  
That's great...thanks for posting a good example of how you got it to work!

Billy McCafferty
GeneralRe: Exceptions being thrown when mapping 2 objects together from different DB's
driskel
12:27 29 Apr '08  
Your welcome, I appreciate the dialog, the article and the additional help you provided.

One thing I ran into was db names having too many segments.

for example: xxx.xxx.xxx.xxx.dbo would cause issues due to the name can only have 3 segments, or at least that was what I was running into.
GeneralRe: Exceptions being thrown when mapping 2 objects together from different DB's
driskel
12:38 29 Apr '08  
One other note,   In the config for NHibernate you can define the default_schema property for one of the databases and remove the schema declaration in the mappings that use that database.
GeneralRe: Exceptions being thrown when mapping 2 objects together from different DB's : One negative
driskel
12:44 29 Apr '08  
A negative by-product of this approach is that in at least some of your mappings you are tying the mapping to schema verses a configuration element.   It could be an issue from a test/production setup.
AnswerRe: Exceptions being thrown when mapping 2 objects together from different DB's : One negative
driskel
5:52 6 May '08  
A way to solve the issue highlighted is to create different mapping assemblies and define which ones are used in the nhibernate configuration.

The caveat to that is duplicate mapping files except for the schema property and the possible bugs associated to this issue.
QuestionMultiple database problem and questions?
Lin Peng
5:00 22 Feb '08  
Bill,

Excellent work and really appreciate the enterprise sample for NHibernate. When I tried to apply 2 databases (Northwind and Pubs), I got "Item has already been added" error and here are my config settings:
********************************************************************
web.config:
<nhibernateSettings>
<!-- List every session factory that will be needed; transaction management and closing sessions
will be managed with the open-session-in-view module -->
<sessionFactories>
<clearFactories />
<sessionFactory name="northwind" factoryConfigPath="C:\downloads\NHibernate\NHibernateBestPractice\EnterpriseSample\EnterpriseSample.Web\Config\NorthwindNHibernate.config" isTransactional="true" />
<sessionFactory name="pubs" factoryConfigPath="C:\downloads\NHibernate\NHibernateBestPractice\EnterpriseSample\EnterpriseSample.Web\Config\PubsNHibernate.config" isTransactional="true" />
</sessionFactories>
</nhibernateSettings>
************************************************************
config folder contains:
CastleComponents.config
NorthwindNHibernate.config
PubsNHibernate.config
*************************************************************
CastleComponents.config contains:
<configuration>
<components>
<component id="primaryDaoFactory"
type="EnterpriseSample.Data.NHibernateDaoFactory, EnterpriseSample.Data"
service="EnterpriseSample.Core.DataInterfaces.IDaoFactory, EnterpriseSample.Core">
<parameters>
<sessionFactoryConfigPath>C:\downloads\NHibernate\NHibernateBestPractice\EnterpriseSample\EnterpriseSample.Web\Config\NorthwindNHibernate.config</sessionFactoryConfigPath>
<sessionFactoryConfigPath>C:\downloads\NHibernate\NHibernateBestPractice\EnterpriseSample\EnterpriseSample.Web\Config\PubsNHibernate.config</sessionFactoryConfigPath>
</parameters>
</component>
</components>
</configuration>
***************************************************************
NorthwindNHibernate.config contains:
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2" >
<session-factory name="northwind">
<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string">Data Source=(local);Database=Northwind;User ID=sa;Password=password;</property>
<property name="connection.isolation">ReadCommitted</property>
<property name="default_schema">Northwind.dbo</property>
<!-- HBM Mapping Files -->
<mapping assembly="EnterpriseSample.Core" />
</session-factory>
</hibernate-configuration>
**************************************************************
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2" >
<session-factory name="pubs">
<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string">Data Source=(local);Database=pubs;User ID=sa;Password=password;</property>
<property name="connection.isolation">ReadCommitted</property>
<property name="default_schema">pubs.dbo</property>
<!-- HBM Mapping Files -->
<mapping assembly="EnterpriseSample.Core" />
</session-factory>
</hibernate-configuration>
************************************************************
Server Error in '/EnterpriseSample.Web' Application.
--------------------------------------------------------------------------------

Item has already been added. Key in dictionary: 'sessionFactoryConfigPath' Key being added: 'sessionFactoryConfigPath'
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.ArgumentException: Item has already been added. Key in dictionary: 'sessionFactoryConfigPath' Key being added: 'sessionFactoryConfigPath'

Source Error:


Line 37: // Supplying "XmlInterpreter" as the parameter tells Windsor
Line 38: // to look at web.config for any necessary configuration.
Line 39: windsorContainer = new WindsorContainer(new XmlInterpreter());
Line 40: }
Line 41:


Source File: c:\downloads\NHibernate\NHibernateBestPractice\EnterpriseSample\EnterpriseSample.Web\App_Code\CustomHttpApplication.cs Line: 39


**********************************************************************************************

I think the problem is the CastleComponents.config. Do you know what is the problem?
Do you have multiple database example to show how to configure?
How can I dynamically pass "sessionFactoryConfigPath"?

Thank you very much.
AnswerRe: Multiple database problem and questions?
Billy McCafferty
9:09 25 Feb '08  
Lin,

Here's an altered example of my configuration...

Within web.config:

<?xml version="1.0"?>
<configuration>
<configSections>
<section name="nhibernateSettings" type="MyReusableProject.Data.NHibernateSession.OpenSessionInViewSection, MyReusableProject.Data"/>
</configSections>

<nhibernateSettings>
<!-- List every session factory that will be needed; transaction management and closing sessions
will be managed with the open-session-in-view module -->
<sessionFactories>
<clearFactories/>
<sessionFactory name="db1" factoryConfigPath="C:\MyApp.Web\Config\Db1NHibernate.config" isTransactional="true"/>
<sessionFactory name="db2" factoryConfigPath="C:\MyApp.Web\Config\Db2NHibernate.config" isTransactional="false"/>
</sessionFactories>
</nhibernateSettings>

Within Db1NHibernate.config:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.0" >
<session-factory name="ipcs">
<property name="hibernate.connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="hibernate.dialect">NHibernate.Dialect.MsSql2000Dialect</property>
<property name="hibernate.connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="hibernate.connection.connection_string">Data Source=MyDbServer;Database=MyDb;User ID=MyUsername;Password=MyPassword;</property>
<property name="hibernate.connection.isolation">ReadCommitted</property>
<property name="hibernate.default_schema">MyDb.dbo</property>
<property name="hibernate.cache.provider_class">NHibernate.Caches.SysCache.SysCacheProvider,NHibernate.Caches.SysCache</property>
<property name="hibernate.cache.use_query_cache">true</property>
<!-- HBM Mapping Files -->
<mapping assembly="MyApp.Core" />
</session-factory>
</hibernate-configuration>

Db2NHibernate.config looks almost identical, but points to a different DB and loads different HBM mapping files.

Hope this helps!

Billy McCafferty
GeneralRe: Multiple database problem and questions?
Lin Peng
8:00 27 Feb '08  
Bill,

Thanks a lot for your quick response. I followed your instructions, and got below problem:

1. The CastleComponents.config defines all the objects which are available for dependency injection, if I add PubsNHibernate.config into CastleComponents.config:
<configuration>
<components>
<component id="primaryDaoFactory"
type="EnterpriseSample.Data.NHibernateDaoFactory, EnterpriseSample.Data"
service="EnterpriseSample.Core.DataInterfaces.IDaoFactory, EnterpriseSample.Core">
<parameters>
<sessionFactoryConfigPath>C:\downloads\NHibernate\NHibernateBestPractice\EnterpriseSample\EnterpriseSample.Web\Config\NorthwindNHibernate.config</sessionFactoryConfigPath>
</parameters>
</component>
<component id="secondaryDaoFactory"
type="EnterpriseSample.Data.NHibernateDaoFactory, EnterpriseSample.Data"
service="EnterpriseSample.Core.DataInterfaces.IDaoFactory, EnterpriseSample.Core">
<parameters>
<sessionFactoryConfigPath>C:\downloads\NHibernate\NHibernateBestPractice\EnterpriseSample\EnterpriseSample.Web\Config\PubsNHibernate.config</sessionFactoryConfigPath>
</parameters>
</component>
</components>
</configuration>

2. the ListAuthors.aspx(my new page to access pubs.dbo.authors table) works, but ListCustomers.aspx (accwssing northwind.dbo.Customers table) got below error message, it looks like that it takes the last component: PubsNHibernate.config, do you know why?

Server Error in '/EnterpriseSample.Web' Application.
--------------------------------------------------------------------------------

Invalid object name 'pubs.dbo.Customers'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Data.SqlClient.SqlException: Invalid object name 'pubs.dbo.Customers'.

Source Error:


Line 51: }
Line 52:
Line 53: return criteria.List<T>() as List<T>;
Line 54: }
Line 55:


Source File: C:\downloads\NHibernate\NHibernateBestPractice\EnterpriseSample\ProjectBase.Data\AbstractNHibernateDao.cs Line: 53

I don't understand how CastleComponents works. If you can post a working multiple database enterprise example(Northwind and pubs databases) in code project for reference, that will be excellent.

Thank you very much.

Lin Peng
GeneralIs it possible to map 1 class to 2 databases
filipa.andrade
1:48 1 Feb '08  
Hi,

I am working with 2 databases in a project. One is a legacy database and the other is a new database where we want to store only some data from the other.

I am using your NHibernateSessionManager and it works fine with both databases, my question is how can I have different mapping files that map the same class but to the 2 different databases. Is it possible? For example I have the class Cat that in the legacy database has an id, name, color, etc and in the new database only has id and name. I want to use the same Cat class being able to, according to the current session, map to the new or the old database.

I tried adding the mapping property to each session-factory config file like in the NHibernate documentation:
<mapping resource="Core.Cat.hbm.xml" assembly="Core.Cat" />

The error now is:
Duplicate class/entity mapping Cat

Is it possible to do what I need?
Can anyone give me some help.

Thanks a lot!
GeneralRe: Is it possible to map 1 class to 2 databases
balazs_hideghety
23:55 6 Feb '08  
I'm a beginner to NHibernate. But i think it's not possible to use the same class for 2 databases. I recommend to create 2 separate assemblies with classes - 1st assemlby for the first, 2nd for the second database. The assemblies may have the "same" classes.

If you want to copy Users from DB1 to DB2, you'll have to:
1) get DB1.User[]
2) then create new DS2.User[] classes
3) copy content from DB1.User[] to DB2.User[]
4) persist DB2.User[]

This should work without any problem.

C#, ASPX, SQL, novice to NHibernate

AnswerRe: Is it possible to map 1 class to 2 databases
Billy McCafferty
10:13 19 Feb '08  
I don't think it's possible to do exactly what you're trying to do. Alternatively, you could create a public Cat and a protected CatDetail objects that would each have an HBM to different databases. You could then include CatDetail as a many-to-one relationship from Cat and expose CatDetail properties from Cat so that other classes could work with Cat, not knowing the CatDetail is behind the scenes.

Billy McCafferty
GeneralSample Super Class for Test Setup?
Dave Turkel
18:46 12 Oct '07  
I had an Abstract class for the 1st edition (single DB) version of this code... does anyone have anything that I can quickly apply to this multiple DB version?

Thanks in advance,

Dave


Last Updated 25 Aug 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010