使用 NHibernate 和 Autofac 管理多个数据库

发布于 2024-10-07 18:39:47 字数 2621 浏览 3 评论 0原文

我想我会在自己思考解决方案的同时提出这个问题。

在构建了大部分应用程序后,我在最后一刻要求支持读/写附加数据库(总共 2 个,没有已知的其他数据库)。我使用 NHibernate 构建了该应用程序,并使用 Autofac 提供 DI/IoC 组件。 FWIW,它驻留在 ASP.NET MVC 2 应用程序中。

我有一个通用的存储库类,它接受 NHibernate 会话。理论上,只要传递给它的会话是从适当的 SessionFactory 生成的,我就可以继续为第二个数据库使用这个通用存储库 (IRepository<>),对吗?

好吧,当应用程序启动时,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();

其中返回基本 SessionFactory 的ConfigureNHibernate() 看起来像:

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

目前,这仅限于一个数据库。在任何其他 NHib 场景中,我可能会将单独的 SessionFactories 的实例推入哈希中,并根据需要检索它们。我不想重新架构整个事情,因为我们已经非常接近主要版本了。所以,我猜我至少需要修改上面的方法,以便我可以独立配置两个SessionFactory。我的灰色区域是我将如何指定与特定存储库(或至少对于特定于第二个数据库的实体)一起使用的正确工厂。

任何人在以这种方式使用 IoC 容器和 NHibernate 时都有过这种情况的经验吗?

编辑 我已经删除了一个 GetSessionFactory 方法,该方法采用配置文件路径,检查 HttpRuntime.Cache 中是否存在匹配的 SessionFactory,如果尚不存在则创建一个新实例,然后返回该 SessionFactory。现在我仍然需要弄清楚如何告诉 Autofac 如何以及何时指定适当的配置路径。新方法如下所示(大量借用了 Billy 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;
    }

I thought I'd get this question out there while I noodled on a solution on my own.

After having built out the bulk of an application, I have a last minute requirement to support reading/writing to an additional database (2 total, no known others). I built the application using NHibernate, with Autofac supplying the DI/IoC components. FWIW, this resides in an ASP.NET MVC 2 app.

I have a generic repository class that takes an NHibernate session. Theoretically, I can continue to use this generic repository (IRepository<>) for the second database so long as the session that gets passed to it is spawned from an appropriate SessionFactory, right?

Well, when the app starts, Autofac does it's thing. With regards to the Session and SessionFactory, I have a module that states:

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();

where ConfigureNHibernate(), which returns the base SessionFactory, looks like:

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

Currently, this is limited to just the one database. In any other NHib scenario, I'd likely shove instances of the separate SessionFactories into a hash, and retrieve them as needed. I don't want to have to re-architect the whole thing as we're fairly close to a major release. So, I'm guessing I need to modify at least the methods above so that I can independently configure two SessionFactories. My gray area is how I'll go about specifying the correct Factory be used with a specific repository (or at least for entities specific to that second database).

Anyone have experience with this scenario while using an IoC container and NHibernate in this manner?

EDIT
I've stubbed out a GetSessionFactory method that takes a configuration file path, checks for the existance of a matching SessionFactory in the HttpRuntime.Cache, creates a new instance if one doesn't already exist, and returns that SessionFactory. Now I still need to hammer out how to tell Autofac how and when to specify an appropriate config path. The new method looks like (borrowed heavily from Billy's 2006 post here):

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;
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

从此见与不见 2024-10-14 18:39:47

我假设您希望不同类型的实体进入每个数据库;如果您想在每个数据库中保留相同类型的实体,请查看 AutofacContrib.Multitenant。

有助于解决此方案的两个要素是:

首先,使用命名服务来引用两个不同的数据库。我将它们称为“db1”和“db2”。与数据库相关的所有组件,一直到会话,都使用一个名称进行注册:

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.

现在,假设您有一个接受 ISessionNHibernateRepository类型code> 作为其构造函数参数,并且您可以编写一个返回 "db1""db2" 的函数 WhichDatabase(TypeEntityType)当给定实体的类型时。

我们使用 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 将选择适当的会话,并且 Autofac 将继续延迟初始化并正确处置会话。

当然,您必须仔细考虑事务管理。

希望这能起到作用!
注意

I'm assuming that you want different types of entities to go into each database; if you want to keep the same kinds of entities in each database, check out AutofacContrib.Multitenant.

The two ingredients that can help with this scenario are:

First, use named services to refer to the two different databases. I'll call them "db1" and "db2". All of the components relating to the database, all the way up to the session, get registered with a name:

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.

Now, assuming you have a type NHibernateRepository<T> that accepts an ISession as its constructor parameter, and that you can write a function WhichDatabase(Type entityType) that returns either "db1" or "db2" when given the type of an entity.

We use a ResolvedParameter to dynamically choose the session based on the entity type.

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])));

(Warning - compiled and tested in Google Chrome ;))

Now, resolving IRepository<MyEntity> will select the appropriate session, and sessions will continue to be lazily initialised and correctly disposed by Autofac.

You will have to think carefully about transaction management of course.

Hope this does the trick!
NB

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文