背景
我们正在使用ASP.Net Core和Entity Framework Core 2.2构建Web应用程序
我们正在连接到旧数据库。设置是有16个数据库,所有数据库都具有完全相同的架构,并包含不同的上下文数据。我们无法更改。
我们需要根据请求参数在运行时连接到特定数据库。
想象一下,母公司下的每个业务都有一个数据库。
想象一下,每个数据库都有诸如Employee,Client和Shift(员工为客户工作的班次)之类的表。
还有一个“中央”数据库,其中包含所有其他数据库(例如设置等)共有的信息。
我们要求在一个列表视图中列出所有业务的所有员工。
我们计划通过使用中央数据库中的SQL View来检索此数据,该视图仅对其他每个数据库都执行UNION(如果您有关于如何更好地做到这一点的建议,请共享它)。
CREATE VIEW dbo.v_all_employees AS
SELECT EmployeeId, Fullname, 1 AS BusinessId FROM BusinessA.dbo.Employees
UNION ALL SELECT EmployeeId, Fullname, 2 AS BusinessId FROM BusinessB.dbo.Employees
-- etc. etc.
我们有一组模型,它们代表所有数据库中的所有实体(表),因为它们共享完全相同的模式,例如一班员工班级,一班客户班级等。
用例
用户通过以下途径转到网页上查看所有企业的员工列表:
然后,用户单击单个员工的“详细信息”链接以查看更多详细信息,将用户带到url:
http://example.org/employees/details?employeeId=123&businessId=xyz
我要坚持的是,在给定BusinessId的情况下,如何在运行时实例化特定于业务的DbContext。
我想出了三种方法(现在是@mylee,这是第四种)来达到预期的结果,并希望从社区中获得反馈。
每个选项都假定每个DbContext将实现一个接口,该接口公开所有DbSets和DbContext方法,然后我们将使用Factory模式来确定要使用哪个DbContext实现。
第一个选项: 只需让工厂根据请求参数“ bizId”创建正确的DbContext。
但是,这要求每个DbContext都重写OnConfiguring方法并设置DbProvider-dotnet Core框架通过其IoC容器扩展方法AddDbContext为我们所做的事情:
public class ContextFactory : IContextFactory
{
public IBIZContext GetContext(int bizId)
{
switch (bizId)
{
// Newing-up the DbContexts like this requires that the OnConfiguring method
// for each context be present in each DbContext to setup the DbProvider
// with the correct connection string.
case 6:
return new BIZ_AContext();
case 7:
return new BIZ_BContext();
default:
throw new Exception("Unexpected Business Id");
}
}
}
这个问题是我不喜欢我们在这里更新上下文的方式。它要求我们在每个上下文中都重写OnConfiguring方法,并有权访问连接字符串。
第二个选项:
我更喜欢使用Startup.cs中设置的内置IoC容器,但这显示了服务定位器反模式。 另外,它将HttpContext从Web项目泄漏到Infrastructure项目中(我使用的是Onion体系结构):
public class ContextFactoryUsingLocator : IContextFactoryUsingLocator
{
public IBIZContext GetContext(IHttpContextAccessor httpContextFactory, int bizId)
{
// Injecting the HttpContextAccessor gives us access to the IoC Container via RequestServices;
// But using it here exhibits the Service Locator anti-pattern.
// Perhaps its ok to use the Service Locator pattern within a Factory in this case?
switch (bizId)
{
case 6:
return (BIZ_AContext)httpContextFactory.HttpContext.RequestServices.GetService(typeof(BIZ_AContext));
case 7:
return (BIZ_BContext)httpContextFactory.HttpContext.RequestServices.GetService(typeof(BIZ_BContext));
default:
throw new Exception("Unexpected Business Id");
}
}
}
第三个选项
将每个DbContext注入到Factory中,并让Factory简单地返回正确的实例:
public class ContextFactoryInjected : IContextFactoryInjected
{
private readonly BIZ_AContext _bizAContext;
private readonly BIZ_BContext _bizBContext;
public ContextFactoryInjected(
BIZ_AContext bizAContext,
// 14 other DbContext dependencies omitted here for brevity
BIZ_BContext bizBContext)
{
// Injecting all 16 DbContexts into the Factory seems to counter the intention of the Factory since the IoC Container
// would be managing the creation of all the instances; isn't that the responsibility of the Factory?
// More importantly; wouldn't this have serious performance implications, creating 16 instances of a DbContext on every Request?
_bizAContext = bizAContext;
_bizBContext = bizBContext;
}
public IBIZContext GetContext(int bizId)
{
switch (bizId)
{
case 6:
return _bizAContext;
case 7:
return _bizBContext;
default:
throw new Exception("Unexpected Business Id");
}
}
}
第四个选项 在工厂内封装DbContext的配置(此方法由@mylee建议)
public class ContextFactoryConfigured : IContextFactoryConfigured
{
public IBIZContext GetContext(int bizId)
{
switch (bizId)
{
// Newing-up the DbContexts like this encapsulates all the details required for the DbContext within the Factory
case 6:
var bizAOptionsBuilder = new DbContextOptionsBuilder<BizAContext>();
bizAOptionsBuilder.UseSqlServer(Settings.BizAConnectionString);
return new BizAContext(bizAOptionsBuilder.Options);
case 7:
var bizBOptionsBuilder = new DbContextOptionsBuilder<BizBContext>();
bizBOptionsBuilder.UseSqlServer(Settings.BizBConnectionString);
return new BizBContext(bizBOptionsBuilder.Options);
default:
throw new Exception("Unexpected Business Id");
}
}
}
您是否同意选项2展示了Service Locator反模式,即是否可以说Factory依赖于管理其创建的对象?
您是否认为选项4是其中最好的方法,因为通常“更新”其对象通常是工厂的责任,并且不会导致顾虑混杂(即是否需要HttpContext),并封装了在Factory中构建上下文所需的所有详细信息(例如ConnectionString)?
或者有没有一种方法可以使用依赖注入来实现,而又不会引起关注的混合?
或者还有没有在这里我没有提到的更好的方法?
答案 0 :(得分:3)
我们在使用多个结构相同的数据库的遗留系统中遇到了同样的问题,并提出了与您的选项 4 类似的解决方案:
有一个接口和工厂方法来创建一个dbContext。它接受一个连接字符串作为参数:
public interface ICustomDbContextFactory<out T> where T: DbContext
{
T CreateDbContext(string connectionString);
}
public class CustomDbContextFactory<T> : ICustomDbContextFactory<T> where T: DbContext
{
public T CreateDbContext(string connectionString)
{
var optionsBuilder = new DbContextOptionsBuilder<T>();
optionsBuilder.UseSqlServer(connectionString);
return System.Activator.CreateInstance(typeof(T), optionsBuilder.Options) as T;
}
}
工厂在 DI 注册为单身人士:
services.AddSingleton<ICustomDbContextFactory<CustomDbContext>, CustomDbContextFactory<CustomDbContext>>();
然后你就在需要的时候使用它(需要能够注入工厂):
using (var dbContext = customDbContextFactory.CreateDbContext(connectionString))
{
// use your dbContext here
}
我认为它与您的 nr 非常匹配。 4 解决方案(除了我们有逻辑拉出与工厂分离的连接字符串),我们发现它是我们能解决的问题的最干净的解决方案。想听听您完成了哪些实施,以及是否有人对如何解决该问题有更好的想法。
答案 1 :(得分:0)
为什么不使用dbcontext的构造器?我认为这是达到您想要的更好的方法。
public class DataContext : DbContext
{
public DataContext(string database)
: base("Data Source=********;Initial Catalog=" + database + ";Integrated Security=True")
{
}
}
您需要另一个数据库来保存所有公司的数据。您可以通过以下链接获取更多信息: