如何在EF7 / .NET Core中为多个数据库实现DbContext继承

时间:2017-01-24 13:17:54

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

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

我有许多不同的数据库(针对不同的系统),这些数据库具有配置项,例如配置,用户和组的共同基本模式(总共约25个表)。我试图通过继承基类来避免重复模型共享部分的相当广泛的EF配置,如图所示。

My DbContext inheritance tree

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

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

DbContextOptions<ConfigurationContext>

我无法看到解决方法。

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

3 个答案:

答案 0 :(得分:14)

好的,我的工作方式仍然使用继承层次结构,如下所示(使用上面的InvestContext作为示例):

如上所述,InvestContext类接收类型为DbContextOptions<InvestContext>的构造函数参数,但必须将DbContextOptions<ConfigurationContext>传递给它的基础。

我编写了一个方法,用于从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都适用于简单的查询,但它似乎有点像黑客,可能不是EF7的设计者所想到的。

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

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

  

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

这可以解决问题。

在与该团队成员进行进一步互动(您可以在问题上看到)和一些挖掘EF Core代码之后,上面概述的方法看起来是安全的,并且是最佳方法,直到实施建议的更改。

答案 1 :(得分:1)

根据您的要求,您只需使用非类型特定版本的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>();

答案 2 :(得分:0)

我想引起大家的注意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) {
    }
}