【问题标题】:How do I implement DbContext inheritance for multiple databases in EF7 / .NET Core如何在 EF7 / .NET Core 中为多个数据库实现 DbContext 继承
【发布时间】:2017-01-24 13:17:53
【问题描述】:

我正在 ASP.NET Core 1.1 中构建 Web API。

我有许多不同的数据库(用于不同的系统),它们具有用于配置、用户和组等配置项的通用基本模式(总共大约 25 个表)。我试图通过从图中所示的基类继承来避免为模型的共享部分复制相当广泛的 EF 配置。

但是,这不起作用,因为实体框架 (EF) 要求将 DbContextOptions<DerivedRepository> 作为参数传递给构造函数,其中 DerivedRepository 必须与调用构造函数的存储库的类型匹配。然后必须通过调用:base(param) 将参数向下传递到基DbContext

因此,当(例如)使用 DbContextOptions<InvestContext> 初始化 InvestContext 时,它会调用 base(DbContextOptions<InvestContext>) 并且 EF 会抛出错误,因为对 ConfigurationContext 构造函数的调用正在接收 DbContextOptions<InvestContext> 类型的参数而不是所需的类型DbContextOptions<ConfigurationContext>。由于 DbContext 上的 options 字段被定义为

    private readonly DbContextOptions _options;

我找不到解决办法。

定义一次共享模型并多次使用它的最佳方式是什么?我想我可以创建一个辅助函数并从每个派生上下文中调用它,但它不像继承那样干净或透明。

【问题讨论】:

    标签: entity-framework asp.net-core .net-core entity-framework-core


    【解决方案1】:

    我想提请大家注意this post from the OP's GitHub issue

    通过提供一个使用 DbContextOptions 而没有任何类型的受保护构造函数,我能够在没有破解的情况下解决这个问题。使第二个构造函数受保护可确保它不会被 DI 使用。

    public class MainDbContext : DbContext {
        public MainDbContext(DbContextOptions<MainDbContext> options)
            : base(options) {
        }
    
        protected MainDbContext(DbContextOptions options)
            : base(options) {
        }
    }
    
    public class SubDbContext : MainDbContext {
        public SubDbContext (DbContextOptions<SubDbContext> options)
            : base(options) {
        }
    }
    

    【讨论】:

    • 这应该是公认的答案。在这种情况下,看起来这将作为建议的模式添加到官方文档中。 github.com/aspnet/EntityFramework.Docs/issues/594
    • 经过几个小时的调试和尝试不同的事情,这救了我。像魅力一样工作
    • 哇,我花了好几个小时在这上面。非常感谢。
    • 完美运行。
    【解决方案2】:

    好的,我的工作方式仍然使用继承层次结构,就像这样(使用上面的 InvestContext 作为示例):

    如上所述,InvestContext 类接收DbContextOptions&lt;InvestContext&gt; 类型的构造函数参数,但必须将DbContextOptions&lt;ConfigurationContext&gt; 传递给它的基类。

    我编写了一个方法,该方法从DbContextOptions 变量中挖掘连接字符串,并构建所需类型的 DbContextOptions 实例。 InvestContext 在调用 base() 之前使用此方法将其 options 参数转换为正确的类型。

    转换方法如下:

        protected static DbContextOptions<T> ChangeOptionsType<T>(DbContextOptions options) where T:DbContext
        {
            var sqlExt = options.Extensions.FirstOrDefault(e => e is SqlServerOptionsExtension);
    
            if (sqlExt == null)
                throw (new Exception("Failed to retrieve SQL connection string for base Context"));
    
            return new DbContextOptionsBuilder<T>()
                        .UseSqlServer(((SqlServerOptionsExtension)sqlExt).ConnectionString)
                        .Options;
        }
    

    并且 InvestContext 构造函数调用从这里改变:

      public InvestContext(DbContextOptions<InvestContext> options):base(options)
    

    到这里:

      public InvestContext(DbContextOptions<InvestContext> options):base(ChangeOptionsType<ConfigurationContext>(options))
    

    到目前为止,InvestContext 和 ConfigurationContext 都适用于简单的查询,但它看起来有点像 hack,可能不是 EF7 的设计者想到的。

    当我尝试复杂的查询、更新等时,我仍然担心 EF 会陷入困境。看来这不是问题,见下文)

    编辑:我已将此问题记录为 EF7 团队 here 的问题,并且团队成员建议对 EF Core 核心进行如下更改:

    “我们应该更新检查以允许 TContext 是从当前上下文类型派生的类型”

    这样可以解决问题。

    在与该团队成员(您可以在该问题上看到)进一步互动并深入研究 EF Core 代码后,我在上面概述的方法看起来很安全,并且是实施建议更改之前的最佳方法。

    【讨论】:

      【解决方案3】:

      根据您的要求,您可以简单地使用 DbContextOptions 的非类型特定版本。

      改变这些:

      public ConfigurationContext(DbContextOptions<ConfigurationContext> options):base(options)    
      public InvestContext(DbContextOptions<InvestContext> options):base(options)
      

      到这里:

      public ConfigurationContext(DbContextOptions options):base(options) 
      public InvestContext(DbContextOptions options):base(options)
      

      然后,如果您首先创建您的 ConfigurationContext,那么继承它的类似乎会获得相同的配置。它还可能取决于您初始化不同上下文的顺序。

      编辑: 我的工作示例:

      public class QueryContext : DbContext
      {
          public QueryContext(DbContextOptions options): base(options)
          {
          }
      }
      
      public class CommandContext : QueryContext
      {
          public CommandContext(DbContextOptions options): base(options)
          {
          }
      }
      

      在 Startup.cs 中

      services.AddDbContext<CommandContext>(options =>
                       options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
      
      services.AddDbContext<QueryContext>(options =>
                      options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
      

      或者,在测试类中:

          var connectionString = "Data Source=MyDatabase;Initial Catalog=MyData;Integrated Security=SSPI;";
      
          var serviceProvider = new ServiceCollection()
              .AddDbContext<QueryContext>(options => options.UseSqlServer(connectionString))
              .BuildServiceProvider();
      
          _db = serviceProvider.GetService<QueryContext>();
      

      【讨论】:

      • 您实际上是否有执行此操作的代码,如果有,您运行的是哪个版本的 EF7?因为当我尝试这个 DbContextOptions 是抽象的,即使我实现了派生类 (DbCo),我也会在调用构造函数时遇到编译时错误:“DbCo 不能分配给参数类型 DbContextOptions”。在 DbContext 构造函数的 EF7 代码库中查看 options.ContextType.GetTypeInfo().IsAssignableFrom(GetType().GetTypeInfo()),因此 DbContextOptions 必须是类型为 DerivedContext 的泛型派生。
      • 是的,我在我的应用程序和测试主机配置中都有这个工作。我正在使用Microsoft.EntityFrameworkCore 1.1.0。您可能需要从上下文构造函数中的类型中删除 &lt;DerivedContext&gt;
      • 我已经编辑了我的问题,添加了我尝试实施您的建议的详细信息(评论中没有足够的空间)。鉴于我在 EntityFramework 代码库中看到的检查,我得到了我预期的错误。据我所知,避免该错误的唯一方法是调用base() 而不是base(options),我认为如果您正在初始化多个上下文,这会导致其他问题。您能否使用显示 DerivedContext 定义和用法的更全面的代码示例来扩展您的答案?谢谢!
      • 添加了我的示例。我看不出有多大区别,但可能会有所帮助。
      • 好的,是的,我对这个here 做了相当多的摆弄,它适用于一些不是很明显的严重警告。首先,对 AddDbContext 的每次调用似乎都向 DI 注册了 DbContextOptions,这意味着最后注册的上下文将是传递给 all DbContexts 的 DbContextOptions 的类型,其构造函数指定了非泛型参数。含义:所有上下文都获得相同的选项(包括连接字符串);从 DbContext 继承的上下文必须最后注册; (见下一条评论)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-06
      • 1970-01-01
      • 2021-11-16
      • 2016-12-17
      • 2014-02-21
      • 1970-01-01
      • 2015-02-12
      相关资源
      最近更新 更多