Autofac 多租户数据库配置

Autofac Multitenant Database Configuration

我有一个基础抽象上下文,它有几百个共享对象,然后是 2 个 "implementation" 上下文,它们都继承自基础,旨在供 .net 核心应用程序中的不同租户使用。租户对象被注入到 OnConfiguring 的构造函数中,以选择要使用的连接字符串。

public abstract class BaseContext : DbContext
{
    protected readonly AppTenant Tenant;

    protected BaseContext (AppTenant tenant)
    {
        Tenant = tenant;
    }
}

public TenantOneContext : BaseContext
{
   public TenantOneContext(AppTenant tenant) 
        : base(tenant)
    {
    }
}

在 startup.cs 中,我这样注册 DbContext:

services.AddDbContext<TenantOneContext>();
services.AddDbContext<TenantTwoContext>();

然后使用 autofac 容器和 th Multitenant 包,我像这样注册租户特定上下文:

IContainer container = builder.Build();

MultitenantContainer mtc = new MultitenantContainer(container.Resolve<ITenantIdentificationStrategy>(), container);

mtc.ConfigureTenant("1", config =>
{
    config.RegisterType<TenantOneContext>().AsSelf().As<BaseContext>();
});

mtc.ConfigureTenant("2", config =>
{
    config.RegisterType<TenantTwoContext>().AsSelf().As<BaseContext>();
});

Startup.ApplicationContainer = mtc;

return new AutofacServiceProvider(mtc);

我的服务层是围绕注入的 BaseContext 设计的,以便在可能的情况下重用,然后需要特定功能的服务使用 TenantContexts。

public BusinessService
{
   private readonly BaseContext _baseContext;

   public BusinessService(BaseContext context) 
   {
       _baseContext = context;
   }
}

在运行时的上述服务中,我得到一个异常"No constructors on type 'BaseContext' can be found with the constructor finder 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'"。我不确定为什么这会被破坏......AppTenant 肯定是创建的,因为我可以成功地将它注入其他地方。如果我添加额外的注册,我可以让它工作:

builder.RegisterType<TenantOneContext>().AsSelf().As<BaseContext>();

我不明白为什么租户容器注册需要上述注册才能工作。这对我来说似乎很糟糕;在 structuremap (Saaskit) 中,我能够在不添加额外注册的情况下执行此操作,并且我假设使用内置的 AddDbContext 注册会负责为要覆盖的容器创建默认注册。我在这里遗漏了什么或者这可能是 autofac 的多租户功能中的错误?

更新:

这里是问题的完全可运行的回购:https://github.com/danjohnso/testapp

如果我有第 53/54 行和第 82-90 行,为什么还需要 Startup.cs 的第 66 行?

如我所料,您的问题与多租户无关。你几乎完全正确地实现了它,你是对的,你不需要额外的注册,顺便说一句,这两个(下面)也是因为你稍后在租户的范围内注册它们:

        services.AddDbContext<TenantOneContext>();
        services.AddDbContext<TenantTwoContext>();

所以,您在 TenantIdentitifcationStrategy 实施中只犯了一个非常小但非常重要的错误。让我们来看看如何创建容器 - 这主要是针对可能 运行 遇到此问题的其他人。我只会提到相关部分。

首先,TenantIdentitifcationStrategy 与其他内容一起注册到容器中。由于没有生命周期范围的明确规范,默认情况下它被注册为 InstancePerDependency() - 但这并不重要,正如您将看到的那样。接下来,"standard" IContainer 由 autofac 的 buider.Build() 创建。此过程的下一步是创建 MultitenantContainer,它采用 ITenantIdentitifcationStrategy 的实例。这意味着 MultitenantContainer 及其俘虏依赖项 - ITenantIdentitifcationStrategy - 将是单例 ,无论 ITenantIdentitifcationStrategy 如何在容器中注册。在你的情况下,它从那个标准的 "root" 容器中解析出来,以便管理它的依赖关系——好吧,无论如何,这就是 autofac 的用途。一般来说,这种方法一切都很好,但这是您的问题真正开始的地方。当 autofac 解析此实例时,它会完全按照预期的方式执行操作 - 将所有依赖项注入 TenantIdentitifcationStrategy 的构造函数,包括 IHttpContextAccessor。因此,就在构造函数中,您从该上下文访问器中获取 IHttpContext 的实例并将其存储以供在租户解析过程中使用 - 这是一个致命的错误:此时没有 http 请求,并且由于 TenantIdentitifcationStrategy 是一个单例,这意味着永远不会有它! 因此,它在整个应用程序生命周期中获得 null 请求上下文。这实际上意味着 TenantIdentitifcationStrategy 将无法根据 http 请求解析租户标识符 - 因为它实际上并不分析它们。因此,MultitenantContainer 将无法解析任何特定于租户的服务。

现在当问题很清楚时,它的解决方案是显而易见的和微不足道的 - 只需将请求上下文 context = _httpContextAccessor.HttpContext 的获取移动到 TryIdentifyTenant() 方法。它在适当的上下文中被调用,并且能够访问请求上下文并对其进行分析。

PS。由于我完全不知道 autofac 的多租户概念,所以这次挖掘对我来说很有教育意义,所以非常感谢你提出这样一个有趣的问题! :)

PPS。还有一件事:这个问题只是一个完美的例子,说明准备充分的例子有多重要。你提供了很好的例子。没有它,没有人能够弄清楚问题是什么,因为它最重要的部分没有出现在问题中——有时你只是不知道这部分到底在哪里……