我有一个基本的抽象上下文,它有几百个共享对象,然后是两个“实现”上下文,它们都是从基继承的,并被设计为供.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容器和th多租户包注册租户特定的上下文,如下所示:
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;
}
}在运行时的上述服务中,我得到一个异常“在构造函数查找器'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'".中找不到'BaseContext‘类型上的构造函数。我不知道为什么要创建broken....the AppTenant,因为我可以成功地将它注入其他地方。如果我加了一个额外的注册,我可以让它工作:
builder.RegisterType<TenantOneContext>().AsSelf().As<BaseContext>();我不明白为什么需要进行上述登记才能使租户集装箱注册工作。在我看来,这似乎是坏的;在structuremap (Saaskit)中,我能够在不添加额外注册的情况下做到这一点,并且我假设使用内置的AddDbContext注册将负责为容器创建一个默认的注册来覆盖。我是在这里遗漏了什么,还是这可能是autofac的multitenat功能中的一个bug?
更新:
以下是问题的完全可运行的回购:https://github.com/danjohnso/testapp
如果我有53/54行和82-90行,为什么需要Startup.cs的第66行?
发布于 2018-04-24 23:47:32
正如我所预期的,您的问题与这种多租户无关。您几乎完全正确地实现了它,而且您是对的,您不需要额外的注册,而且,顺便说一句,这两个(下面)也是因为稍后您在租户的作用域中注册了它们:
services.AddDbContext<TenantOneContext>();
services.AddDbContext<TenantTwoContext>();因此,您在TenantIdentitifcationStrategy实现中只犯了一个非常小但非常重要的错误。让我们来看看如何创建容器--这主要是为了其他可能遇到这个问题的人。我只提相关的部分。
首先,TenantIdentitifcationStrategy和其他东西一起在容器中注册。由于没有明确的生存期范围规范,因此默认情况下它被注册为InstancePerDependency() --但正如您将看到的那样,这并不重要。接下来,“标准”IContainer由autofac的buider.Build()创建。这个过程的下一步是创建MultitenantContainer,它接受ITenantIdentitifcationStrategy的一个实例。这意味着无论MultitenantContainer是如何在容器中注册的,ITenantIdentitifcationStrategy及其专用依赖项-- ITenantIdentitifcationStrategy -都将是单节点。在您的示例中,它是从标准的“根”容器中解析出来的,以便管理它的依赖关系--不管怎样,这就是autofac的用途。一般情况下,这种方法很好,但这是问题的真正开始。当autofac解析此实例时,它将完全按照预期执行--将所有依赖项注入到TenantIdentitifcationStrategy的构造函数中,包括IHttpContextAccessor。因此,在构造函数中,您可以从上下文访问器中获取一个IHttpContext实例,并将其存储在租户解析过程中使用----这是一个致命的错误:此时没有http请求,而且由于是单个实例,这意味着永远不会有用于它的实例!因此,它将获得整个应用程序生命周期内的null请求上下文。这实际上意味着TenantIdentitifcationStrategy将无法基于http请求解析租户标识符--因为它并不实际分析它们。因此,MultitenantContainer将无法解决任何特定于租户的服务。
现在,当问题清楚时,它的解决方案是显而易见的和琐碎的--只需将请求上下文context = _httpContextAccessor.HttpContext的获取转移到TryIdentifyTenant()方法即可。它在适当的上下文中被调用,并能够访问请求上下文并对其进行分析。
PS。自从我完全不知道autofac的多租户概念以来,这个挖掘对我来说是非常有教育意义的,所以非常感谢您提出这样一个有趣的问题!)
PPS。还有一件事:这个问题只是一个完美的例子,说明了充分准备的例子是多么重要。你举了个很好的例子。没有它,就没有人能够找出问题的症结所在,因为问题中没有提到最重要的部分--有时你不知道这部分实际上在哪里.
https://stackoverflow.com/questions/49950032
复制相似问题