StructureMap, NHibernate and multiple databases

若如初见. 提交于 2019-12-03 00:29:31
Andy S

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.

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;
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!