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

2024-02-06

背景

我们正在使用 ASP.Net Core 和 Entity Framework Core 2.2 构建一个 Web 应用程序

我们正在连接到旧数据库。设置是有 16 个数据库,全部具有完全相同的架构,保存不同的上下文数据。我们无法改变这一点。

我们需要在运行时根据请求参数连接到特定数据库。

想象一下,母公司下的每个业务都有一个数据库。

想象一下,每个数据库都有诸如“员工”、“客户”和“轮班”(员工为客户工作的轮班)等表。

还有一个“中央”数据库,其中保存所有其他数据库通用的信息,例如设置等。

我们需要在单个列表视图中列出所有企业的所有员工。

我们计划通过在中央数据库中使用 SQL 视图来检索此数据,该视图只需在每个其他数据库之间执行 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.

我们有一组模型,代表所有数据库中的所有实体(表),因为它们共享完全相同的模式,例如一个 Employee 类、一个 Client 类等。

Use case

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

http://example.org/employees http://example.org/employees

然后,用户单击单个员工的“详细信息”链接以查看更多详细信息,从而将用户带到以下 URL:

我所困惑的是,在给定 BusinessId 的情况下,我们如何在运行时实例化特定于业务的 DbContext。

我想出了三种方法(现在是第四种,感谢@mylee)来实现预期的结果,并正在寻求社区的反馈。

每个选项都假设每个 DbContext 将实现一个公开所有 DbSets 和 DbContext 方法的接口,然后我们将使用工厂模式来确定要使用哪个 DbContext 实现。

第一个选项:只需让工厂根据请求参数“bizId”创建正确的 DbContext 即可。

但是,这要求每个 DbContext 重写 OnConfiguring 方法并设置 DbProvider - dotnet Core 框架通过其 IoC 容器扩展方法为我们做的事情添加数据库上下文 https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext#using-dbcontext-with-dependency-injection:

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 项目泄漏到基础设施项目中(我使用的是 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的配置封装在Factory中(此方法是@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 展示了服务定位器反模式,即工厂依赖于其管理创建的对象是否正确?

您是否认为选项 4 是其中最好的方法,因为工厂通常有责任“更新”其对象,并且不会导致问题混合(即不需要HttpContext )并且它封装了在工厂内构建上下文所需的所有细节(例如 ConnectionString)?

或者有没有一种方法可以使用依赖注入来实现这一点,而不会导致问题混合?

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


我们在使用多个相同结构的数据库的遗留系统中遇到了同样的问题,并提出了与您的选项 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 解决方案(除了我们有从工厂中取出连接字符串的逻辑这一事实),我们发现它是我们能找到的问题的最干净的解决方案。想听听您完成了哪些实施,以及是否有人有更好的想法如何解决该问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在运行时根据请求参数创建EF Core DbContext 的相关文章

随机推荐

  • 我怎样才能取消 Future.delayed 函数调用

    我怎样才能取消 Future delayed 我正在使用 Future delayed 来执行某些任务 但是如果我想取消这个延迟的任务 那么他们的任何方法或任何其他要使用的东西也是如此 Future delayed Duration sec
  • 将密钥和 SSL 证书导入 java 密钥库

    我有 pem 格式的证书文件和私钥文件 是否可以使用 Java 代码和 JDK 附带的默认库 而不是第三方库 将这些文件加载 到 Java 密钥库中 当我尝试谷歌搜索时 我只能找到 der 格式的文件以使用 java 代码导入到 java
  • Python 异常传播

    我正在构建一个工具 当异常向上传播时 有关异常上下文的新数据将添加到异常中 问题是 当异常到达顶层时 所有额外的上下文数据都在那里 但只显示最新的堆栈跟踪 有没有一种简单的方法可以让异常显示抛出异常的原始堆栈跟踪而不是最后一个堆栈跟踪 或者
  • 无法在导入请求时从“集合”导入名称“映射”[重复]

    这个问题在这里已经有答案了 Python版本 Python 3 10 4 画中画版本 pip 22 0 4 所以我试图用套接字制作一个小项目 我添加了一个上传文件的功能 但是每当我导入请求时 它都会抛出此错误 下面是我运行的代码 Trace
  • USB 控制传输能否保证交付?

    USB 2 0 指定 http www usb org developers docs usb20 docs 4 种传输类型 第 5 4 节传输类型 控制转移 同步传输 中断传输 批量转账 第 5 8 节规定批量转账提供 在带宽可用的基础上
  • 如何将数据从一种表单发送到另一个类的函数

    我知道如何将数据从一种表单传递到另一种表单 但如何将数据从一种表单传递到类 这是我想要传递的数据form1上课问题 string GrpID somevalue string DurationID somevalue 我搜索过但没有得到确切
  • Android java 设置图像资源

    我对此有很大的了解 if num1 getText equals 0 num1 setText 1 ImageView hpdown1 ImageView findViewById R id hair hpdown1 setImageRes
  • 通过代码使用 NHibernate 映射:当 IDENTITY_INSERT 设置为 OFF 时,无法在表“DietUser”中插入标识列的显式值

    我花了一段时间才找到这个问题的答案 所以我想分享这份爱 当通过 SQL Server 代码使用 NHibernate 的新映射时 我无法保存实体 保存实体时 会引发 System Data SqlClient SqlException 并显
  • 我们应该在 C++ 中使用 exit 吗?

    根据C 参考 exit正常终止进程 执行常规清理 用于终止程序 正常的程序终止执行以下操作 以相同的顺序 与当前线程关联并具有线程存储的对象 持续时间被破坏 仅限 C 11 具有静态存储的对象 持续时间被破坏 C 并且使用 atexit 注
  • 如何更改 pine 脚本中函数的全局变量?

    我正在尝试编写一个脚本来获得 9 个级别的江恩平方 我已经用其他语言完成了它 但无法理解此处的 pine 脚本 它说无法修改函数中的全局变量 有什么解决方案可以获取这里的值是我的脚本 version 4 study title Volume
  • Xcode 5.1 破坏了一些测试

    自从更新到 Xcode 5 1 以来 我在尝试运行单元测试 XCTestSuite 时遇到了错误 当我运行单独的测试方法时 出现错误Error while reading test scope bundle在控制台上没有结果 当我运行测试文
  • 如何在整个组织内共享数据

    组织在多个部门和应用程序之间共享关键数据有哪些好方法 举个例子 假设有一个主要应用程序和数据库来管理客户数据 组织中还有十个其他应用程序和数据库读取该数据并将其与自己的数据关联起来 目前 这种数据共享是通过数据库 DB 链接 物化视图 触发
  • 如何以最有效的方式将图像转换为字符串?

    我想将图像文件转换为字符串 以下作品 MemoryStream ms new MemoryStream Image1 Save ms ImageFormat Jpeg byte picture ms ToArray string formm
  • 如果索引列不同,则对一列的值求和?

    当索引列不同时 如何对一列中的值求和 最初 我有这样的 SQL 查询 SELECT COALESCE SUM ISNULL cast Quantity as int 0 0 AS QuantitySum FROM Records 也尝试这样
  • CDate 可以在没有 for 循环的 Range 上使用吗?

    我有一个列 其格式如下 Range A A NumberFormat dd mm yyyy 现在 我正在寻找一个有效的版本 For k 1 To Range A1 End xlDown Row from first to last row
  • 使用 zkcli 从 ZooKeeper 删除/删除 Solr 配置?

    我们在 ZK 中有一些旧的 Solr 配置 我们需要清理 删除 配置位于 configs AAA configs BBB 等 configs 下还有其他配置 Solr zkcli sh 脚本和文档没有提供删除配置的指导 唯一看起来明显的是
  • 获取当前活动 Xamarin 表单

    我使用 Leadtools SDK 进行条码阅读器 当我尝试获取当前活动时 它给我 null 这是我的代码 Stream resourceStream new MemoryStream Droid MainActivity activity
  • Thymeleaf:如何使用 Thymeleaf 在 JavaScript 中使用布尔运算符

    我正在使用 thymeleaf 在 javascript 中使用th inline javascript 但是当我们在 javascript thymeleaf 中添加布尔条件时 会抛出异常 如下所示 org xml sax SAXPars
  • 如何修复这个批处理文件? (打字机效果)

    我想让批处理脚本具有打字机风格 效果 同时删除文件并显示回声和其他内容 我希望它看起来像这样 Write1 echo Deleting Prefetches del s q C Windows Prefetch JAVAW pf echo
  • 在运行时根据请求参数创建EF Core DbContext

    背景 我们正在使用 ASP Net Core 和 Entity Framework Core 2 2 构建一个 Web 应用程序 我们正在连接到旧数据库 设置是有 16 个数据库 全部具有完全相同的架构 保存不同的上下文数据 我们无法改变这