我有一个基本的抽象上下文,它有几百个共享对象,然后是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行?
答案 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。还有一件事:这个问题只是一个完美的例子,说明准备好的例子有多重要。你提供了很好的例子。没有它,没有人能够弄清楚问题是什么,因为问题的最重要部分没有出现在问题中 - 有时候你只是不知道这个部分究竟在哪里......