我正在使用ASP.NET 5和EF7编写多租户Web服务。存储库数据特定于租户/用户。因为我将在所有调用中使用TenantId,所以我想用当前的TenantId初始化Repository。
public class MyRepository {
private int tenantId;
public MyRepository(MyDbContext context, int tenantId) {
this.tenantId = tenantId;
// ...
}
public Task<List<Data>> GetAllAsync() {
return this.context.Data.Where(d => d.TenantId == this.tenantId).ToListAsync();
}
}
public class Startup {
public void ConfigureServices(IServiceCollection services) {
// How do I pass the TenantId to the constructor?
services.AddScoped<MyRepository>();
}
}
在用户通过身份验证后,是否可以初始化我的存储库一次?我如何通过构造函数传递TenantId?
您的租户和用户ID值都是运行时数据,您不应该将运行时数据注入系统的组件 (在这种情况下是您的存储库),因为这极大地使您的组合根复杂化(正如您已经体验过的那样)并使其几乎不可能验证您的对象图。运行时数据应该流经已构建的对象图。实现这一点基本上有两种方法。您可以通过组件的公共API的方法调用来传递数据,也可以注入一个负责在请求时检索运行时值的组件。
在您的情况下,后一种选择是最好的。因此,我的建议是注入一个允许检索此上下文信息的组件:
public interface ITenantContext
{
int CurrentTenantId { get; }
}
public interface IUserContext
{
int CurrentUserId { get; }
}
public class MyRepository {
private readonly Func<MyDbContext> contextProvider;
private readonly ITenantContext tentantContext;
public MyRepository(Func<MyDbContext> contextProvider, ITenantContext tentantContext){
this.contextProvider = contextProvider;
this.tentantContex = tentantContex;
}
public Task<List<Data>> GetAllAsync() {
return this.contextProvider().Data
.Where(d => d.TenantId == this.tenantContext.CurrentTenantId)
.ToListAsync();
}
这样可以优雅地解决问题,因为现在您可以定义ITenantContext
实现,该实现知道如何为当前请求检索正确的租户ID。例如:
public sealed class AspNetSessionTenantContext : ITenantContext {
public int CurrentTenantId {
get { return (int)HttpContext.Current.Session["tenantId"]; }
}
}
这甚至允许您将所有组件注册为单例,这可以提高性能,并减少常见DI陷阱的更改,例如强制依赖性 。