Autofac多租户数据库配置

时间:2018-04-20 21:34:49

标签: asp.net-core entity-framework-core autofac multi-tenant

我有一个基本的抽象上下文,它有几百个共享对象,然后是2"实现"上下文,它们都从基础继承而来,旨在供.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中,我注册了DbContexts:

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

然后使用autofac容器和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;
   }
}

在运行时的上述服务中,我得到一个异常&#34;没有类型&#39; BaseContext&#39;的构造函数。可以使用构造函数查找程序找到&#39; Autofac.Core.Activators.Reflection.DefaultConstructorFinder&#39;&#34;。我不确定为什么这会被破坏...... AppTenant肯定会被创建,因为我可以成功地将其注入其他地方。如果我添加额外的注册,我可以使它工作:

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

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

更新

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

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

1 个答案:

答案 0 :(得分:1)

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

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

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

首先,TenantIdentitifcationStrategy与其他东西一起在容器中注册。由于没有明确的生命周期范围规范,因此默认情况下会将其注册为InstancePerDependency() - 但这并不重要,因为您会看到。接下来,“标准”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。还有一件事:这个问题只是一个完美的例子,说明准备好的例子有多重要。你提供了很好的例子。没有它,没有人能够弄清楚问题是什么,因为问题的最重要部分没有出现在问题中 - 有时候你只是不知道这个部分究竟在哪里......