StructureMap, NHibernate and multiple databases
Asked Answered
M

2

7

I'm working on an Asp.Net MVC 3 application using Fluent NHibernate. I'm just attempting to add an IoC container using StructureMap.

I have implemented a custom controller factory which uses StructureMap to create the controller and inject dependencies. Each controller constructor takes one or more services, which in turn take a DAO as constructor argument. Each DAO constructor takes an ISessionFactory.

For my StructureMap NHibernate registry I have the following:

internal class NHibernateRegistry : Registry
{
    public NHibernateRegistry()
    {
        var connectionString = ConfigurationManager.ConnectionStrings["AppDb"].ConnectionString;

        For<ISessionFactory>()
                .Singleton()
                .Use(x => new AppSessionFactory().GetSessionFactory(connectionString));

        For<ISession>()
            .HybridHttpOrThreadLocalScoped()
            .Use(x => x.GetInstance<ISessionFactory>().OpenSession());
    }

}

public class AppSessionFactory
{
    public ISessionFactory GetSessionFactory(string connectionString)
    {
        return GetConfig(connectionString)
                .BuildSessionFactory();
    }

    public static FluentConfiguration GetConfig(string connectionString)
    {
        return Fluently.Configure()
            .Database(MsSqlConfiguration.MsSql2005.ConnectionString(x => x.Is(connectionString)))
            .Mappings(
                x => x.FluentMappings.AddFromAssemblyOf<AppEntity>());
    }
}

This all works fine for a single database and single session factory. However the application uses multiple databases.

What is the best way to handle this?

Midwest answered 28/10, 2011 at 14:52 Comment(0)
S
9

Registering multiple session factories is easy - the problem is selecting the right one when you need it. For example, let's say we have some sort of laboratory that has multiple databases. Each lab has a Location and multiple Samples for that location. We could have a SampleRepository that models that. Each Location has a unique key to identify it (e.g. "LabX", "LabY", "BlackMesa"). We can use that unique key as the name of the database connection string in the app.config file. In this example, we would have three connection strings in the app.config file. Here's a sample connectionStrings section:

<connectionStrings>
  <add name="LabX" connectionString="Data Source=labx;User ID=someuser;Password=somepassword"/>
  <add name="LabY" connectionString="Data Source=laby;User ID=someuser;Password=somepassword"/>
  <add name="BlackMesa" connectionString="Data Source=blackmesa;User ID=freemang;Password=crowbar"/>
</connectionStrings>

Thus, we need to have a unique session factory for each connection string. Let's create a NamedSessionFactory that wraps ISessionFactory:

public interface INamedSessionFactory
{
    public string Name { get; } // The name from the config file (e.g. "BlackMesa")
    public ISessionFactory SessionFactory { get; }
}

public class NamedSessionFactory : INamedSessionFactory
{
    public string Name { get; private set; }
    public ISessionFactory SessionFactory { get; private set; }

    public NamedSessionFactory(string name, ISessionFactory sessionFactory)
    {
        Name = name;
        SessionFactory = sessionFactory;
    }
}

Now we need to modify your AppSessionFactory a bit. First off, what you've created is a session factory factory - that's not quite what we're looking for. We want to give our factory a location and get a session out of it, not a session factory. Fluent NHibernate is what gives us session factories.

public interface IAppSessionFactory
{
    ISession GetSessionForLocation(string locationKey);
}

The trick here is accept a list of INamedSessionFactory objects in the constructor. StructureMap should give us all of the INamedSessionFactory objects that we've registered. We'll get to registration in a second.

public class AppSessionFactory : IAppSessionFactory
{
    private readonly IList<INamedSessionFactory> _factories;

    public AppSessionFactory(IEnumerable<INamedSessionFactory factories)
    {
        _factories = new List<INamedSessionFactory>(factories);
    }

This is where the magic happens. Given a location key, we run through our list of factories looking for one with the same name as locationKey, then ask it to open a session and return it to the caller.

    public ISession GetSessionForLocation(string locationKey)
    {
        var sessionFactory = _factories.Where(x => x.Name == locationKey).Single();

        return sessionFactory.OpenSession();
    }
}

Now let's wire this all together.

internal class NHibernateRegistry : Registry
{
    public NHibernateRegistry()
    {

We're going to loop through all of the connection strings in our app.config file (there would be three of them in this example) and register an INamedSessionFactory object for each one.

        foreach (ConnectionStringSettings location in ConfigurationManager.ConnectionStrings)
        {
            For<INamedSessionFactory>()
                .Singleton()
                .Use(x => new NamedSessionFactory(
                    location.Name,
                    GetSessionFactory(location.ConnectionString));
        }

We also need to register IAppSessionFactory.

        For<IAppSessionFactory>()
          .Singleton()
          .Use<AppSessionFactory>();
    }

You'll notice that we've moved this logic out of the factory class... These are helper methods for creating session factories from Fluent NHibernate.

    private static ISessionFactory GetSessionFactory(string connectionString)
    {
        return GetConfig(connectionString)
                .BuildSessionFactory();
    }

    public static FluentConfiguration GetConfig(string connectionString)
    {
        return Fluently.Configure()
            .Database(MsSqlConfiguration.MsSql2005.ConnectionString(x => x.Is(connectionString)))
            .Mappings(
                x => x.FluentMappings.AddFromAssemblyOf<AppEntity>());
    }
}

That should do it! Let's create a repository for getting at our samples...

public class SampleRepository
{
    private readonly IAppSessionFactory _factory;

    public SampleRepository(IAppSessionFactory factory)
    {
        _factory = factory;
    }

    public IEnumerable<Sample> GetSamplesForLocation(Location location)
    {
        using (ISession session = _factory.GetSessionForLocation(location.Key)
        {
            foreach (Sample sample in session.Query<Sample>())
              yield return sample;
        }
    }
}

Now you can get a single instance of SampleRepository and use the GetSamplesForLocation method to pull samples from any of the three databases we have registered in app.config. Might want to avoid BlackMesa though. I understand there were problems there.

Stemware answered 1/11, 2011 at 16:55 Comment(2)
I should probably note that it's been years since I've used StructureMap or NHibernate - so I might have screwed something up there. But the basic pattern should be sound. Hope it helps!Stemware
A really useful answer. I'm currently using StrucutreMap's TheInstanceNamed() to assign SessionFactories to DAOs, which seems to work OK. Will look to implementing your suggestions when I have a bit of spare time. Thanks.Midwest
Q
0

Are you sure this thing works? string ISessionFactory

public string ISessionFactory SessionFactory { get; private set; }

should this be

public interface INamedSessionFactory
{
    ISessionFactory SessionFactory { get; set; }
    string Name { get; }
}

public class NamedSessionFactory : INamedSessionFactory
{
    public ISessionFactory SessionFactory { get; set; }
    public string Name { get; private set; }

    public NamedSessionFactory(string Name, ISessionFactory SessionFactory)
    {
        this.Name = Name;
        this.SessionFactory = SessionFactory;
    }
}
Quadrivial answered 20/9, 2013 at 21:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.