使用 Fluent nHibernate 和 Ninject 实现多租户。每个租户一个数据库

2023-11-26

我正在构建一个多租户 Web 应用程序,出于安全考虑,我们需要为每个租户拥有一个数据库实例。所以我有一个用于身份验证的 MainDB 和许多用于应用程序数据的 ClientDB。

我正在使用 Asp.net MVC 与 Ninject 和 Fluent nHibernate。我已经在应用程序开始时在 Ninject 模块中使用 Ninject 和 Fluent nHibernate 设置了 SessionFactory/Session/Repositories。我的会话是 PerRequestScope,存储库也是如此。

我的问题是现在我需要为每个租户实例化一个 SessionFactory (SingletonScope) 实例,只要其中一个租户连接到应用程序,并为每个 Web 请求创建一个新会话和必要的存储库。我对如何做到这一点感到困惑,并且需要一个具体的例子。

情况是这样的。

申请开始:TenantX 的用户输入他的登录信息。 MainDB 的 SessionFactory 被创建并打开到 MainDB 的会话以对用户进行身份验证。然后应用程序创建身份验证 cookie。

租户访问应用程序:租户名称 + ConnectionString 是从 MainDB 中提取的,Ninject 必须为该租户构造一个特定于租户的 SessionFactory (SingletonScope)。 Web 请求的其余部分,所有需要存储库的控制器都将根据该租户的 SessionFactory 注入特定于租户的会话/存储库。

如何使用 Ninject 设置该动态?当我有多个数据库时,我最初使用命名实例,但现在数据库是特定于租户的,我迷失了......


经过进一步研究,我可以给你一个更好的答案。

虽然可以将连接字符串传递给ISession.OpenSession更好的方法是创建自定义ConnectionProvider。最简单的方法是从DriverConnectionProvider并覆盖ConnectionString财产:

public class TenantConnectionProvider : DriverConnectionProvider
{
    protected override string ConnectionString
    {
        get
        {
            // load the tenant connection string
            return "";
        }
    }

    public override void Configure(IDictionary<string, string> settings)
    {
        ConfigureDriver(settings);
    }
}

使用 FluentNHibernate 您可以像这样设置提供程序:

var config = Fluently.Configure()
    .Database(
        MsSqlConfiguration.MsSql2008
            .Provider<TenantConnectionProvider>()
    )

每次打开会话时都会评估 ConnectionProvider,以便您连接到应用程序中的租户特定数据库。

上述方法的一个问题是 SessionFactory 是共享的。如果您仅使用一级缓存(因为它与会话相关),那么这并不是真正的问题,但如果您决定启用二级缓存(与 SessionFactory 相关),那么这实际上并不是问题。

因此,推荐的方法是为每个租户设置一个 SessionFactory(这适用于每个租户模式和每个租户数据库策略)。

另一个经常被忽视的问题是,虽然二级缓存与 SessionFactory 绑定在一起,但在某些情况下缓存空间本身是共享的(参考)。这可以通过设置提供者的“regionName”属性来解决。

下面是根据您的要求的 SessionFactory-per-tenant 的工作实现。

The Tenant类包含我们为租户设置 NHibernate 所需的信息:

public class Tenant : IEquatable<Tenant>
{
    public string Name { get; set; }
    public string ConnectionString { get; set; }

    public bool Equals(Tenant other)
    {
        if (other == null)
            return false;

        return other.Name.Equals(Name) && other.ConnectionString.Equals(ConnectionString);
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Tenant);
    }

    public override int GetHashCode()
    {
        return string.Concat(Name, ConnectionString).GetHashCode();
    }
}

因为我们将存储一个Dictionary<Tenant, ISessionFactory>我们实施IEquatable接口,以便我们可以评估租户密钥。

获取当前租户的过程抽象如下:

public interface ITenantAccessor
{
    Tenant GetCurrentTenant();
}

public class DefaultTenantAccessor : ITenantAccessor
{
    public Tenant GetCurrentTenant()
    {
        // your implementation here

        return null;
    }
}

最后NHibernateSessionSource它管理会话:

public interface ISessionSource
{
    ISession CreateSession();
}

public class NHibernateSessionSource : ISessionSource
{
    private Dictionary<Tenant, ISessionFactory> sessionFactories = 
        new Dictionary<Tenant, ISessionFactory>();

    private static readonly object factorySyncRoot = new object();

    private string defaultConnectionString = 
        @"Server=(local)\sqlexpress;Database=NHibernateMultiTenancy;integrated security=true;";

    private readonly ISessionFactory defaultSessionFactory;
    private readonly ITenantAccessor tenantAccessor;

    public NHibernateSessionSource(ITenantAccessor tenantAccessor)
    {
        if (tenantAccessor == null)
            throw new ArgumentNullException("tenantAccessor");

        this.tenantAccessor = tenantAccessor;

        lock (factorySyncRoot)
        {
            if (defaultSessionFactory != null) return;

            var configuration = AssembleConfiguration("default", defaultConnectionString);
            defaultSessionFactory = configuration.BuildSessionFactory();
        }
    }

    private Configuration AssembleConfiguration(string name, string connectionString)
    {
        return Fluently.Configure()
            .Database(
                MsSqlConfiguration.MsSql2008.ConnectionString(connectionString)
            )
            .Mappings(cfg =>
            {
                cfg.FluentMappings.AddFromAssemblyOf<NHibernateSessionSource>();
            })
            .Cache(c =>
                c.UseSecondLevelCache()
                .ProviderClass<HashtableCacheProvider>()
                .RegionPrefix(name)
            )
            .ExposeConfiguration(
                c => c.SetProperty(NHibernate.Cfg.Environment.SessionFactoryName, name)
            )
            .BuildConfiguration();
    }

    private ISessionFactory GetSessionFactory(Tenant currentTenant)
    {
        ISessionFactory tenantSessionFactory;

        sessionFactories.TryGetValue(currentTenant, out tenantSessionFactory);

        if (tenantSessionFactory == null)
        {
            var configuration = AssembleConfiguration(currentTenant.Name, currentTenant.ConnectionString);
            tenantSessionFactory = configuration.BuildSessionFactory();

            lock (factorySyncRoot)
            {
                sessionFactories.Add(currentTenant, tenantSessionFactory);
            }
        }

        return tenantSessionFactory;
    }

    public ISession CreateSession()
    {
        var tenant = tenantAccessor.GetCurrentTenant();

        if (tenant == null)
        {
            return defaultSessionFactory.OpenSession();
        }

        return GetSessionFactory(tenant).OpenSession();
    }
}

当我们创建一个实例时NHibernateSessionSource我们为我们的“默认”数据库设置了一个默认的 SessionFactory。

When CreateSession()被称为我们得到一个ISessionFactory实例。这将是默认会话工厂(如果当前租户为空)或特定于租户的会话工厂。定位租户特定会话工厂的任务由GetSessionFactory method.

最后我们打电话OpenSession on the ISessionFactory我们获得的实例。

请注意,当我们创建会话工厂时,我们设置 SessionFactory 名称(用于调试/分析目的)和缓存区域前缀(出于上述原因)。

我们的 IoC 工具(在我的例子中为 StructureMap)将所有内容连接起来:

    x.For<ISessionSource>().Singleton().Use<NHibernateSessionSource>();
    x.For<ISession>().HttpContextScoped().Use(ctx => 
        ctx.GetInstance<ISessionSource>().CreateSession());
    x.For<ITenantAccessor>().Use<DefaultTenantAccessor>();

这里 NHibernate Session Source 的作用域是单例和每个请求的 Session。

希望这可以帮助。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 Fluent nHibernate 和 Ninject 实现多租户。每个租户一个数据库 的相关文章

  • 使用实体框架重叠约会

    我将 asp net mvc 与实体框架一起使用 我有一个包含 startat 字段 endat 字段和 roomid 字段 称为 SpaceConfigurationId 的约会列表 并且希望查找给定房间已重复预订的约会列表 可以假设 e
  • 如何强制 Visual Studio 2008 生成 Designer.cs,例如不管怎样.aspx.designer.cs

    我在使用 Visual Studio 2005 使用网站技术生成的 Asp Net V2 0 中有一些 Web 表单 想要将它们导入到设置为 v3 5 Asp Net MVC 的 Visual Studio 2008 我在其中使用项目技术
  • 在 nHibernate 关系中使用实体的 Lite 版本?

    在某些情况下 出于性能原因 创建一个实体的轻量级版本 指向同一个表 但映射的列较少 这是一个好主意吗 例如 如果我有一个包含 50 列的联系人表 并且在一些相关实体中 我可能对 FirstName 和 LastName 属性感兴趣 那么创建
  • ASP.NET MVC,控制器可以改变提交的值吗?

    ASP NET MVC 中是否允许更改提交的值 HttpPost public ActionResult Create Person toCreate toCreate Lastname toCreate Lastname A return
  • 流畅的 Nhibernate 枚举映射

    我有一些问题enum流畅的 NHibernate 中的映射 我知道这个问题已被问过很多次 但我找不到任何适合我的解决方案 我是 NHibernate 的新手 看起来我可能错过了一些简单而愚蠢的事情 这是我的代码 public class D
  • 良好的 WiX 编辑器 [重复]

    这个问题在这里已经有答案了 我目前正在开发一个使用 WiX 创建 MSI 的项目 我过去在 Sourceforge 上使用 WiXEdit 来管理包含在 WiX 项目中的文件 因为它比直接操作 XML 稍微容易一些 但它仍然有点笨重 有谁知
  • 从 MVC 控制器调用 Web API

    我的 MVC 5 项目解决方案中有一个 Web API 控制器 WebAPI 有一个方法可以将特定文件夹中的所有文件作为 Json 列表返回 name file1 zip path c 从我的 HomeController 我想调用这个方法
  • 在 Dapper 中处理 Oracle 数据库连接

    我正在尝试连接到 Oracle 数据库并尝试执行查询 下面是我的模型类 using System using System Collections Generic using System Linq using System Web usi
  • 如何以一种形式发布两个或多个模型?

    我正在为一个项目开发互联网课程计划应用程序 该课程计划是根据以下模型构建的 使用数据库优先方法中的实体框架生成 public partial class Subject public int Id get set public string
  • MVC 重定向到没有控制器的视图

    希望应该是一个简单的 我创建了一个通用错误视图 当整个站点的操作方法内发生异常时 我想显示该视图 我创建了一个部分页面 所有导航都位于其中 因此我不需要在此视图上使用控制器 那么如何从控制器内的操作方法重定向到它 像这样的东西 HttpPo
  • Visual Studio IIS 工作正常,但在 IIS 7 脚本中托管时获取错误的 URL?

    Hi 我有一个 ASP NET MVC 站点 我在 MasterPage 中使用以下链接
  • 关于实体框架上下文生命周期的问题

    我对 ASP NET MVC 应用程序中实体框架上下文的所需生命周期有一些疑问 让上下文在尽可能短的时间内保持活动状态不是最好的吗 考虑以下控制器操作 public ActionResult Index IEnumerable
  • ASP.NET MVC ActionFilterAttribute 在模型绑定之前注入值

    我想创建一个自定义操作过滤器属性 该属性在模型绑定期间可访问的 HttpContext 项中添加一个值 我尝试将其添加到 OnActionExecuting 中 但似乎模型绑定是在过滤器之前执行的 你知道我该怎么做吗 也许模型绑定器中有一个
  • jQuery UI 对话框 + 验证

    我在单击 保存 后使用 Jquery Validate 验证 jQuery UI 对话框时遇到问题 这是我创建 Jquery 对话框的代码 它从目标 href URL 加载对话框 document ready dialogForms fun
  • 单元测试:创建“模拟”请求来模拟 MVC 页面请求

    如何为我的 asp net mvc 应用程序创建模拟请求以进行单元测试 我有什么选择 我在我的操作中使用 FormsCollection 因此我也可以模拟表单输入数据 您只需创建 FormCollection 的新实例并在其中添加数据即可
  • mvc4 捆绑包,它是如何工作的?

    在 mvc4 中 他们使用捆绑包来调用所有脚本和 css 文件一次 据我所知 调用js和cs文件时 它们的顺序很重要 如果我使用捆绑包 我如何知道捆绑包内的 css 和 js 文件的顺序是否正确 我可以定制订购吗 我现在的日期选择器有问题
  • 为什么 Docker 不支持多租户?

    我看了这个关于 Docker 的 YouTube 视频 https www youtube com watch v vb7U 9AO7Ww22 00 演讲者 Docker 产品经理 说道 您可能会想 Docker 不支持多租户 您是对的 但
  • 如何在 ASP.NET MVC 中处理会话数据

    假设我想存储一个名为language id在会议中 我想我也许可以做如下的事情 public class CountryController Controller WebMethod EnableSession true AcceptVer
  • MVC BaseController 处理 CRUD 操作

    我想重构我的基本 CRUD 操作 因为它们非常重复 但我不确定最好的方法 我的所有控制器都继承 BaseController 如下所示 public class BaseController
  • MVC - 应用程序根目录在使用 Url.Content/Url.Action 的 url 中出现两次

    我在同一个域上有几个 mvc 应用程序 每个应用程序都有自己的目录 mydomain com app1mydomain com app2 etc 在根级别使用 Url Content 和 Url Action 时 app1 部分在 url

随机推荐

  • 在 PHP 中迭代复杂的关联数组

    有没有一种简单的方法可以在 PHP 中迭代此结构的关联数组 数组 searches有一个编号索引 包含 4 到 5 个关联部分 所以我不仅需要迭代 searches 0 通过 searches n 但是也 searches 0 part0
  • 获取有关 MATLAB com.mathworks 内部结构的帮助

    可以访问 MATLAB 的内部 java 代码位 以编程方式更改 MATLAB 本身 例如 您可以使用以下命令以编程方式在编辑器中打开文档 editorServices com mathworks mlservices MLEditorSe
  • UITableView自定义滚动条

    如何为 UITableView 创建自定义滚动条 我想删除跟踪开始时弹出并在跟踪结束时消失的默认选项 相反 我想要一个类似于计算机程序中的程序 a 它位于屏幕的右侧并且永久可见 b 手动滚动栏会将 UITableView 滚动到适当的位置
  • 禁用特定 GDI 设备上下文的抗锯齿功能

    我正在使用第三方库将图像渲染到 GDI DC 并且我需要确保渲染任何文本时都不会进行任何平滑 抗锯齿 以便我可以将图像转换为具有索引颜色的预定义调色板 我用于渲染的第三方库不支持此功能 并且仅根据当前 Windows 设置的字体渲染来渲染文
  • 如何在 Haskell 中使用策略编写并行归约?

    在高性能计算中 总和 乘积等通常使用 并行归约 来计算 该方法需要n元素并在 O logn 时间 给定足够的并行性 在 Haskell 中 我们通常使用fold对于这种计算 但评估时间始终与列表的长度呈线性关系 Data Parallel
  • 固定宽度整数类型是否保证是标准内置类型的 typedef?

    类型是否来自
  • 如何在写入后清除 PrintWriter 的内容

    晚上好 我想知道如何清除写入PrintWriter的数据 即打印后是否可以从PrintWriter中删除数据 在此 servlet 中 我将一些文本打印到响应中 并在 表示的行处 我想删除所有以前打印的数据并打印新内容 protected
  • Kivy 布局高度适应子部件的高度

    我想创建一个布局 其中有类似于 BoxLayout 的内容 以便我能够在布局中创建 行 并且在每个 行 中我想使用另一个 BoxLayout 中的某些内容来创建 列 列不需要均匀分布 例如 我想创建一个 BoxLayout 其中一列带有方形
  • 如何在codeigniter中的hmvc中的另一个模块中加载模型?

    我想在我的项目中使用模块化扩展 HMVC 如下所示 modules module01 models models01 php controllers controller01 php views views01 php module02 m
  • 我应该切换到Python吗? [关闭]

    很难说出这里问的是什么 这个问题模棱两可 含糊不清 不完整 过于宽泛或言辞激烈 无法以目前的形式合理回答 如需帮助澄清此问题以便重新打开 访问帮助中心 我最近一直在考虑转向Python编程语言 目前 Matlab 是我所在部门用于快速开发和
  • 在 WPF C# TreeView 中获取子节点的父节点

    我知道使用 WPF 进行 C 编程与传统的 C 程序不同 因此大多数在线材料都没有说明我需要什么 我的 WPF 窗口中有一个 TreeView 控件 其中有父节点和子节点 我想将它们存储在 Node 类型的列表中 id name paren
  • 如果命令绑定解析为 null,为什么按钮会启用?

    好的 XAML 非常简单 使用 MVVM 绑定到ICommand SomeCommand get 视图模型上的属性
  • 我的程序如何从 ASCII 切换到 Unicode?

    我想用 C 编写一个可以在 Unix 和 Windows 上运行的程序 该程序应该能够使用 Unicode 和非 Unicode 环境 它的行为应该仅取决于环境设置 我想要的一个很好的功能是操作从目录中读取的文件名 这些可以是 unicod
  • 如何在C++中读取格式化数据?

    我已将数据格式化如下 Words 5 AnotherWord 4 SomeWord 6 它在一个文本文件中 我使用 ifstream 来读取它 但如何分离数字和单词 该单词仅由字母组成 单词和数字之间会有一定的空格或制表符 不确定有多少 假
  • 为什么 CDATA 在脚本标签下被注释掉?

    我正在读这个question我有一个相关的问题 这家伙here说 它用在脚本标签中以避免解析 already CDATA Question 1 如果脚本是already CDATA 为什么它 在脚本标签下 仍然呈现为 CDATA Quest
  • Sql Server 中的“IN”子句限制

    有谁知道 IN 子句的表达式列表 用于测试匹配 中可以拥有的值的数量限制是多少 是的 有限制 但是微软仅指定其位于 数千 在 IN 子句中的括号内显式包含大量值 数千个用逗号分隔的值 可能会消耗资源并返回错误 8623 或 8632 要解决
  • 我在哪里可以获得线程安全的 CollectionView?

    在后台线程上更新业务对象集合时 我收到以下错误消息 这种类型的 CollectionView 不支持从与 Dispatcher 线程不同的线程更改其 SourceCollection 好吧 这是有道理的 但它也引出了一个问题 什么版本的 C
  • Java 中原始整数类型的行为不一致

    有人可以向我解释一下 就像我五岁一样 为什么我在 Java 中表示整数的四种基本类型中的两种会得到不同的行为 AFAIK 所有四个都是有符号的 并且它们都使用最高有效位作为符号位 那么为什么 byte 和 Short 表现正常 而 int
  • 显示进度条,显示表单提交的进度

    这个问题还没有完全解答 欢迎大家踊跃留言 我正在尝试显示一个简单的progress bar提交大表格时 该表单包含十几个字段 以及一些文件上传字段 用户可以在其中选择图片 然后 当他点击Create按钮 提交带有数据和图片的表单 并在数据库
  • 使用 Fluent nHibernate 和 Ninject 实现多租户。每个租户一个数据库

    我正在构建一个多租户 Web 应用程序 出于安全考虑 我们需要为每个租户拥有一个数据库实例 所以我有一个用于身份验证的 MainDB 和许多用于应用程序数据的 ClientDB 我正在使用 Asp net MVC 与 Ninject 和 F