Управление несколькими базами данных с помощью 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.
Два сценария, которые могут помочь в этом сценарии:
- Именованные сервисы http://code.google.com/p/autofac/wiki/TypedNamedAndKeyedServices
- Разрешенный параметр http://code.google.com/p/autofac/wiki/ResolveParameters (минимальные документы по этому вопросу - в следующем выпуске Autofac 2.4 есть некоторые подсластители синтаксиса вокруг этого...)
Во-первых, используйте именованные сервисы для ссылки на две разные базы данных. Я позвоню им "db1"
а также "db2
Msgstr "Все компоненты, относящиеся к базе данных, вплоть до сессии, регистрируются под именем:
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