根据请求参数在运行时创建EF Core DbContext

问题描述 投票:2回答:1

背景

我们正在使用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.

我们有一组模型,它们代表所有数据库中的所有实体(表),因为它们共享完全相同的模式,例如一班员工班级,一班客户班级等。

用例

用户通过此路线转到网页上查看所有企业的员工列表:

http://example.org/employees

用户然后单击单个员工的“详细信息”链接以查看更多详细信息,将用户带到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)?

或者是否有一种方法可以使用依赖注入来实现,而又不会引起关注的混合?

或者还有没有在这里没有提到的更好的方法?

背景我们正在使用ASP.Net Core和Entity Framework Core 2.2构建Web应用程序。我们正在连接到旧数据库。设置是有16个数据库,所有数据库都具有完全相同的...

您为什么不使用dbcontext的构造器?我认为这是达到您想要的更好的方法。

public class DataContext : DbContext { public DataContext(string database) : base("Data Source=********;Initial Catalog=" + database + ";Integrated Security=True") { } }

您需要另一个数据库来保存所有公司的数据。您可以通过此链接获取更多信息:

Multi-Tenancy System With Separate Databases in MVC

c# .net-core factory-pattern service-locator ef-core-2.2
1个回答
0
投票

您为什么不使用dbcontext的构造器?我认为这是达到您想要的更好的方法。

© www.soinside.com 2019 - 2024. All rights reserved.