我正在ASP.NET Core 1.1中构建Web API。
我有许多不同的数据库(针对不同的系统),这些数据库具有配置项,例如配置,用户和组的共同基本模式(总共约25个表)。我试图通过继承基类来避免重复模型共享部分的相当广泛的EF配置,如图所示。
但是,这不起作用,因为实体框架(EF)要求将DbContextOptions<DerivedRepository>
作为参数传递给构造函数,其中DerivedRepository
必须与调用构造函数的存储库类型匹配。然后,必须通过调用DbContext
将参数传递给基础:base(param)
。
因此,当(例如)InvestContext使用DbContextOptions<InvestContext>
进行初始化时,它会调用base(DbContextOptions<InvestContext>)
并且EF会抛出错误,因为对ConfigurationContext
构造函数的调用正在接收类型为{{ 1}}而不是必需的类型DbContextOptions<InvestContext>
。由于DbContext上的选项字段定义为
DbContextOptions<ConfigurationContext>
我无法看到解决方法。
一次定义共享模型并多次使用它的最佳方法是什么?我想我可以创建一个辅助函数并从每个派生的上下文中调用它,但它不像继承那样干净或透明。
答案 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) {
}
}