抱歉,大家。我本应该在此之前发布我的解决方案,但由于某些原因,我没有这样做,现在在这里呈现。
但是
请记住,由于该解决方案尚未经过任何人的审核或生产验证,所以任何地方都可能存在问题,可能我会在这里得到一些反馈。
在项目中,我使用了 ASP .NET Core 1
关于我的数据库结构。我有两个上下文。第一个包含有关用户的信息(包括他们应该访问的数据库模式),第二个包含特定于用户的数据。
在Startup.cs
中,我添加了这两个上下文。
public void ConfigureServices(IServiceCollection
services.AddEntityFrameworkNpgsql()
.AddDbContext<SharedDbContext>(options =>
options.UseNpgsql(Configuration["MasterConnection"]))
.AddDbContext<DomainDbContext>((serviceProvider, options) =>
options.UseNpgsql(Configuration["MasterConnection"])
.UseInternalServiceProvider(serviceProvider));
...
services.Replace(ServiceDescriptor.Singleton<IModelCacheKeyFactory, MultiTenantModelCacheKeyFactory>());
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
请注意UseInternalServiceProvider
的部分,这是由Nero Sule建议的,并附有以下说明:
在EFC 1发布周期的最后,EF团队决定从默认服务集合中删除EF的服务(AddEntityFramework().AddDbContext()),这意味着需要使用EF自己的服务提供程序来解析服务,而不是应用程序服务提供程序。
要强制EF使用您的应用程序服务提供程序,您需要先将EF的服务与数据提供程序一起添加到服务集合中,然后配置DBContext以使用内部服务提供程序。
现在我们需要MultiTenantModelCacheKeyFactory
public class MultiTenantModelCacheKeyFactory : ModelCacheKeyFactory {
private string _schemaName;
public override object Create(DbContext context) {
var dataContext = context as DomainDbContext;
if(dataContext != null) {
_schemaName = dataContext.SchemaName;
}
return new MultiTenantModelCacheKey(_schemaName, context);
}
}
其中DomainDbContext
是包含用户特定数据的上下文
public class MultiTenantModelCacheKey : ModelCacheKey {
private readonly string _schemaName;
public MultiTenantModelCacheKey(string schemaName, DbContext context) : base(context) {
_schemaName = schemaName;
}
public override int GetHashCode() {
return _schemaName.GetHashCode();
}
}
此外,我们需要稍微更改上下文本身,以使其具有模式感知性:
public class DomainDbContext : IdentityDbContext<ApplicationUser> {
public readonly string SchemaName;
public DbSet<Foo> Foos{ get; set; }
public DomainDbContext(ICompanyProvider companyProvider, DbContextOptions<DomainDbContext> options)
: base(options) {
SchemaName = companyProvider.GetSchemaName();
}
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.HasDefaultSchema(SchemaName);
base.OnModelCreating(modelBuilder);
}
}
共享上下文严格与 shared
模式绑定:
public class SharedDbContext : IdentityDbContext<ApplicationUser> {
private const string SharedSchemaName = "shared";
public DbSet<Foo> Foos{ get; set; }
public SharedDbContext(DbContextOptions<SharedDbContext> options)
: base(options) {}
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.HasDefaultSchema(SharedSchemaName);
base.OnModelCreating(modelBuilder);
}
}
ICompanyProvider
负责获取用户模式名称。是的,我知道这段代码离完美还有很远。
public interface ICompanyProvider {
string GetSchemaName();
}
public class CompanyProvider : ICompanyProvider {
private readonly SharedDbContext _context;
private readonly IHttpContextAccessor _accesor;
private readonly UserManager<ApplicationUser> _userManager;
public CompanyProvider(SharedDbContext context, IHttpContextAccessor accesor, UserManager<ApplicationUser> userManager) {
_context = context;
_accesor = accesor;
_userManager = userManager;
}
public string GetSchemaName() {
Task<ApplicationUser> getUserTask = null;
Task.Run(() => {
getUserTask = _userManager.GetUserAsync(_accesor.HttpContext?.User);
}).Wait();
var user = getUserTask.Result;
if(user == null) {
return "shared";
}
return _context.Companies.Single(c => c.Id == user.CompanyId).SchemaName;
}
}
如果我没有漏掉任何东西,那就是这样。现在每个经过身份验证的用户请求都将使用正确的上下文。
希望这有所帮助。