Управление несколькими базами данных с помощью NHibernate и Autofac

Я думал, что я получу этот вопрос там, пока я нашел решение самостоятельно.

После создания основной части приложения у меня есть требование в последнюю минуту поддержать чтение / запись в дополнительную базу данных (всего 2, другие не известны). Я построил приложение, используя NHibernate, и Autofac поставлял компоненты DI/IoC. Кстати, это находится в приложении ASP.NET MVC 2.

У меня есть общий класс репозитория, который принимает сеанс NHibernate. Теоретически я могу продолжать использовать этот общий репозиторий (IRepository<>) для второй базы данных, если сеанс, который ей передается, порождается из соответствующей SessionFactory, верно?

Хорошо, когда приложение запускается, Autofac делает свое дело. Что касается Session и SessionFactory, у меня есть модуль, который заявляет:

builder.Register(c => c.Resolve<ISessionFactory>().OpenSession())
    .InstancePerMatchingLifetimeScope(WebLifetime.Request)
    .OnActivated(e =>
    {
        e.Context.Resolve<TransactionManager>().CurrentTransaction = ((ISession)e.Instance).BeginTransaction();
    });

 builder.Register(c => ConfigureNHibernate())
    .SingleInstance();

где ConfigureNHibernate(), который возвращает базовый SessionFactory, выглядит следующим образом:

private ISessionFactory ConfigureNHibernate()
{
    Configuration cfg = new Configuration().Configure();
    cfg.AddAssembly(typeof(Entity).Assembly);
    return cfg.Configure().BuildSessionFactory();
}

В настоящее время это ограничено только одной базой данных. В любом другом сценарии NHib я, вероятно, помещал бы экземпляры отдельных SessionFactories в хеш и извлекал их по мере необходимости. Я не хочу переделывать всю эту архитектуру, так как мы довольно близки к серьезному выпуску. Итак, я предполагаю, что мне нужно изменить, по крайней мере, описанные выше методы, чтобы я мог независимо настроить две SessionFactories. Моя серая область - как я буду указывать правильную фабрику, которая будет использоваться с конкретным репозиторием (или, по крайней мере, для сущностей, специфичных для этой второй базы данных).

Кто-нибудь имеет опыт работы с этим сценарием при использовании контейнера IoC и NHibernate таким образом?

РЕДАКТИРОВАТЬ Я заглушил метод GetSessionFactory, который принимает путь к файлу конфигурации, проверяет наличие соответствующего SessionFactory в HttpRuntime.Cache, создает новый экземпляр, если он еще не существует, и возвращает этот SessionFactory. Теперь мне все еще нужно выяснить, как сообщить Autofac, как и когда указывать соответствующий путь конфигурации. Новый метод выглядит так (заимствовано из поста Билли 2006 года здесь):

private ISessionFactory GetSessionFactory(string sessionFactoryConfigPath)
    {
        Configuration cfg = null;
        var sessionFactory = (ISessionFactory)HttpRuntime.Cache.Get(sessionFactoryConfigPath);

        if (sessionFactory == null)
        {
            if (!File.Exists(sessionFactoryConfigPath))
                throw new FileNotFoundException("The nhibernate configuration file at '" + sessionFactoryConfigPath + "' could not be found.");

            cfg = new Configuration().Configure(sessionFactoryConfigPath);
            sessionFactory = cfg.BuildSessionFactory();

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

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

        return sessionFactory;
    }

1 ответ

Решение

Я предполагаю, что вы хотите, чтобы разные типы сущностей входили в каждую базу данных; если вы хотите сохранить одинаковые типы сущностей в каждой базе данных, проверьте AutofacContrib.Multitenant.

Два сценария, которые могут помочь в этом сценарии:

Во-первых, используйте именованные сервисы для ссылки на две разные базы данных. Я позвоню им "db1" а также "db2Msgstr "Все компоненты, относящиеся к базе данных, вплоть до сессии, регистрируются под именем:

builder.Register(c => ConfigureDb1())
    .Named<ISessionFactory>("db1")
    .SingleInstance();

builder.Register(c => c.ResolveNamed<ISessionFactory>("db1").OpenSession())
    .Named<ISession>("db1")
    .InstancePerLifetimeScope();

// Same for "db2" and so-on.

Теперь, если у вас есть тип NHibernateRepository<T> который принимает ISession в качестве параметра конструктора, и что вы можете написать функцию WhichDatabase(Type entityType) который возвращает либо "db1" или же "db2" когда дан тип объекта.

Мы используем ResolvedParameter динамически выбирать сеанс на основе типа объекта.

builder.RegisterGeneric(typeof(NHibernateRepository<>))
    .As(typeof(IRepository<>))
    .WithParameter(new ResolvedParameter(
        (pi, c) => pi.ParameterType == typeof(ISession),
        (pi, c) => c.ResolveNamed<ISession>(
            WhichDatabase(pi.Member.DeclaringType.GetGenericArguments()[0])));

(Предупреждение - скомпилировано и протестировано в Google Chrome;))

Теперь, решая IRepository<MyEntity> выберет соответствующий сеанс, и сеансы будут по-прежнему лениво инициализироваться и корректно удаляться Autofac.

Конечно, вам придется тщательно продумать управление транзакциями.

Надеюсь, это сработает! NB

Другие вопросы по тегам