This is about a pending task from long time ago… sorry for those waiting for it.
In the AOP example of CpBT you probably saw a class named SessionFactoryProvider; that class was wrote before CpBT and it can be used in others Contexts (ICurrentSessionContext).
ISessionFactoryProvider
The ISessionFactoryProvider is the contract for the implementation responsible for providing NHibernate’s sessionFactory/ies (big fantasy, no?). ISessionFactoryProvider implements IEnumerable<ISessionFactory> and its more important method is:
ISessionFactory GetFactory(string factoryId);
The parameter factoryId is the name you gave to the session-factory configuration:
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory name="Domain_A">
The parameter factoryId, in this case, should be “Domain_A”.
In uNhAddIns there are two implementations: SessionFactoryProvider and MultiSessionFactoryProvider.
IConfigurationProvider
Both implementations, SessionFactoryProvider and MultiSessionFactoryProvider, have a dependency to an implementation of IConfigurationProvider. The responsibility of an IConfigurationProvider is: provide the set of configured NHibernate’s configurations (again big fantasy).
The contract is:
public interface IConfigurationProvider
{
IEnumerable<Configuration> Configure();
event EventHandler<ConfiguringEventArgs> BeforeConfigure;
event EventHandler<ConfigurationEventArgs> AfterConfigure;
}
In the implementation the BeforeConfigure event should be fired just before call configuration.Configure() of each configuration. You can use the event, for example to use the new NHibernate fluent configuration, or to change some property by code, or to set the ByteCodeProvider, or to use the Fluent-NHibernate configuration way, and so on.
The AfterConfigure event should be fired just after call configuration.Configure() of each configuration. You can use the event to add mappings, create the schema, or integrate it with NHibernate.Validator, or anything else you can do after have a configured NHibernate configuration.
The implementation of Configure method should return instances of NHibernate’s configurations ready to call the BuildSessionFactory method.
In uNhAddIns there are two implementations: DefaultSessionFactoryConfigurationProvider and DefaultMultiFactoryConfigurationProvider.
DefaultSessionFactoryConfigurationProvider
Nothing special to say; basically I can resume its behavior as:
var cfg = new Configuration();
cfg.Configure();
DefaultMultiFactoryConfigurationProvider
For multiple session factories what we need are the names of nhibernate’s config files. The configuration is through appSettings:
<configuration>
<appSettings>
<add key="nhfactory.WhatEverYouWant" value="AppApersistence.cfg.xml" />
<add key="nhfactory.TheOther.NH.configFileName" value="AppBpersistence.cfg.xml" />
As you can see there is only a constant part, the “nhfactory”. The DefaultMultiFactoryConfigurationProvider will iterate all settings looking for those have the key starting with “nhfactory” (what follow is important only for you). The value is the name of the file with each NHibernate configuration. As said above what will be important is the session-factory’s name you will specify inside each configuration.
Custom configuration provider
Obviously you can implement your own configuration provider and use it to inject the behavior to a ISessionFactoryProvider (no matter if you will inject it manually or using a DI framework). You can start your own implementation from scratch or inheriting from AbstractConfigurationProvider or inheriting from one of defaults. An example may look as:
public class MyConfigurationProvider : DefaultMultiFactoryConfigurationProvider
{
public MyConfigurationProvider()
{
AfterConfigure += ConfigureCache;
}
private static void ConfigureCache(object sender, ConfigurationEventArgs e)
{
e.Configuration.QueryCache().ResolveRegion("SearchStatistic")
.Using<TolerantQueryCache>().AlwaysTolerant();
}
}
Configuring your DAOs/Repository for multiple DB
This is the real target of this post. In this example I will show an example using Castle.Windsor.
The start point is that you have, at least, two sessions factories configurations in two files (each one will look as an hibernate.cfg.xml you saw in many examples).
The first
<session-factory name="Domain_A">
and the second
<session-factory name="Domain_B">
The container configuration through XML should look like
<facilities>
<facility id="factorysupport"
type="Castle.Facilities.FactorySupport.FactorySupportFacility, Castle.MicroKernel" />
</facilities>
<component id="sessionFactoryProvider"
service="uNhAddIns.SessionEasier.ISessionFactoryProvider, uNhAddIns"
type="uNhAddIns.SessionEasier.MultiSessionFactoryProvider, uNhAddIns"/>
<component id="domain_a.sessionFactory"
type="NHibernate.ISessionFactory, NHibernate"
factoryId="sessionFactoryProvider"
factoryCreate="GetFactory">
<parameters>
<factoryId>Domain_A</factoryId>
</parameters>
</component>
<component id="domain_b.sessionFactory"
type="NHibernate.ISessionFactory, NHibernate"
factoryId="sessionFactoryProvider"
factoryCreate="GetFactory">
<parameters>
<factoryId>Domain_B</factoryId>
</parameters>
</component>
<component id="domain_a.dao.AnEntity"
service='MyCompany.Data.IDao`1[[MyCompany.AnEntity, MyCompany]], MyCompany.Data'
type='MyCompany.Data.Nh.EntityDao`1[[MyCompany.AnEntity, MyCompany]], MyCompany.Data.Nh'>
<parameters>
<factory>${domain_a.sessionFactory}</factory>
</parameters>
</component>
<component id="domain_a.dao.AnotherEntity"
service='MyCompany.Data.IDao`1[[MyCompany.AnotherEntity, MyCompany]], MyCompany.Data'
type='MyCompany.Data.Nh.EntityDao`1[[MyCompany.AnotherEntity, MyCompany]], MyCompany.Data.Nh'>
<parameters>
<factory>${domain_b.sessionFactory}</factory>
</parameters>
</component>
The are two DAOs each one pointing to a different session factory.
If you prefer the configuration by code it should look as:
private const string Domain_A = "Domain_A";
private const string Domain_B = "Domain_B";
private const string SessionFactoryProviderComponentKey = "sessionFactoryProvider";
...
public void ConfigurePersistence()
{
container = new WindsorContainer();
container.AddFacility<FactorySupportFacility>();
container.Register(Component.For<ISessionFactoryProvider>()
.Named(SessionFactoryProviderComponentKey)
.ImplementedBy<MultiSessionFactoryProvider>());
RegisterSessionFactoryFor(Domain_A);
RegisterSessionFactoryFor(Domain_B);
RegisterEntityDao<AnEntity>(Domain_A);
RegisterEntityDao<AnotherEntity>(Domain_B);
}
private void RegisterSessionFactoryFor(string sessionFactoryName)
{
container.Register(
Component.For<ISessionFactory>()
.Named(GetSessionFactoryProviderKey(sessionFactoryName))
.Configuration(Attrib.ForName("factoryId").Eq(SessionFactoryProviderComponentKey),
Attrib.ForName("factoryCreate").Eq("GetFactory"))
.Parameters(Parameter.ForKey("factoryId").Eq(sessionFactoryName)));
}
private void RegisterEntityDao<T>(string sessionFactoryName) where T : class, IGenericEntity<int>
{
container.Register(
Component.For<IDao<T>>().ImplementedBy<EntityDao<T>>()
.Parameters(
Parameter.ForKey("factory")
.Eq("${" + GetSessionFactoryProviderKey(sessionFactoryName) + "}")));
}
private static string GetSessionFactoryProviderKey(string sessionFactoryName)
{
return sessionFactoryName + ".sessionFactory";
}
Acknowledgments
Special thanks to those customers allow me to share the knowledge they paid.
thank you for sharing this! there appear to be many hidden gems within NH that most of people do not know about. this gives me some guidance in supporting multi-db scenarios moving forward.
ReplyDelete@Jason
ReplyDeleteThese things are in uNhAddIns.
Gran aporte!!
ReplyDeleteNo tendrás algún ejemplo de esta implementación pero sin usar Castle.Windsor?
@bobbher
ReplyDeleteAnd using what ?
nhibernate, pero con el tradicional mapeo, usando .hbm.xml y BuildSessionFactory() normalito... =)
ReplyDeleteHave a look to "NHibernate in WinForm: coupled"
ReplyDeleteCan you give me an example in the multiple DB scenario for generating the schema for Domain_B?
ReplyDeleteSingle Deployment - Multiple Database
ReplyDeletewe run a single instance of the application on a server. We have a master (host) database to store tenant metadata (like tenant name and subdomain) and a separate database for each tenant. Once we identify the current tenant (for example; from subdomain or from a user login form), then we can switch to that tenant's database to perform operations.
Can any one have running example?
Yes and since around 13 years. What you have todo is the implementation/configuration of a custom `DriverConnectionProvider` overriding the method `Configure` and the getter of the property `ConnectionString`.
Delete