我的应用程序在我的DbContext
实现中没有无参数构造函数,我不想为IDbContextFactory<>
实现提供无参数构造函数。
原因是我想要控制DbContext指向的位置。这就是我的所有构造函数都会要求ConnectionStringProvider的原因。
public class MyDbContext : DbContext
{
internal MyDbContext(IConnectionStringProvider provider) : base(provider.ConnectionString) {}
}
和
public class MyContextFactory : IDbContextFactory<MyDbContext>
{
private readonly IConnectionStringProvider _provider;
public MyContextFactory(IConnectionStringProvider provider)
{
_provider = provider;
}
public MyDbContext Create()
{
return new MyDbContext(_provider.ConnectionString);
}
}
我绝对不想添加默认构造函数!我已经这样做了,因为错误的App.config中的连接字符串错误或者假设像DbContext
的默认构造函数那样的默认连接字符串,它在生产时崩溃了。我想在
IConnectionStringProvider
)Add-Migration
脚本DbMigrator.GetPendingMigrations()
目前我收到了一些消息:
上下文工厂类型&#39; Test.MyContextFactory&#39;没有公共无参数构造函数。添加公共无参数构造函数,在上下文程序集中创建IDbContextFactory实现,或使用DbConfiguration注册上下文工厂。
--- --- UPDATE
这可能是How do I inject a connection string into an instance of IDbContextFactory<T>?的副本,但它没有解决方案。我解释原因:
Add-Migration
与连接字符串一起使用,那么我该如何提供消费它的DbContext
或IDbContextFactory<>
?而不是无参数构造函数?
添加迁移MyMigration -ConnectionStringName&#34; MyConnectionString&#34;
DbMigrator.GetPendingMigrations()
也要求无参数DbContext
或IDbContextFactory<>
实施。据我所知,EntityFramework违反了封装 by implying default constructors和原因 temporal coupling这不是故障安全的。所以请提出一个没有无参数构造函数的解决方案。
答案 0 :(得分:1)
好的,我猜这里没有答案!
这就是为什么我要宣布我的胃痛的解决方法:因为没有办法摆脱默认的构造函数(and satisfy principles of encapsulation我提供了一个空构造函数,其中包含故意错误的连接字符串。因此,如果它将用于除迁移之外的任何其他操作,它将尽早在运行时和所有环境(调试/集成/发布)中失败。
public class MyDbContextFactory : IDbContextFactory<MyDbContext>
{
private readonly string _connectionString;
public MyDbContextFactory(string connectionString)
{
_connectionString = connectionString;
}
public MyDbContextFactory()
{
_connectionString = "MIGRATION_ONLY_DONT_USE_ITS_FAKE!";
}
public MyDbContext Create()
{
return new MyDbContext(_connectionString);
}
}
(我不会认为这是一个答案,所以请随意发布一个更好的解决方案。)
答案 1 :(得分:1)
我总是将
Add-Migration
与连接字符串一起使用,那么如何提供消耗它的DbContext
或IDbContextFactory<>
?代替无参数的构造函数?
花了一些时间对实体框架进行反向工程后,答案是:你做不到!
运行Add-Migration
(没有默认构造函数)时,会发生以下情况:
System.Data.Entity.Migrations.Infrastructure.MigrationsException: The target context 'Namespace.MyContext' is not constructible. Add a default constructor or provide an implementation of IDbContextFactory.
at System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration, DbContext usersContext, DatabaseExistenceState existenceState, Boolean calledByCreateDatabase)
at System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration)
at System.Data.Entity.Migrations.Design.MigrationScaffolder..ctor(DbMigrationsConfiguration migrationsConfiguration)
at System.Data.Entity.Migrations.Design.ToolingFacade.ScaffoldRunner.RunCore()
at System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run()
让我们看一下DbMigrator
构造函数。通过Add-Migration
命令运行时,usersContext
为空,configuration.TargetDatabase
为非空,并且包含从命令行参数(例如{{1})传递的信息},-ConnectionStringName
和-ConnectionString
。因此称为-ConnectionProviderName
。
new DbContextInfo(configuration.ContextType, configuration.TargetDatabase)
为了不抛出internal DbMigrator(DbMigrationsConfiguration configuration, DbContext usersContext, DatabaseExistenceState existenceState, bool calledByCreateDatabase) : base(null)
{
Check.NotNull(configuration, "configuration");
Check.NotNull(configuration.ContextType, "configuration.ContextType");
_configuration = configuration;
_calledByCreateDatabase = calledByCreateDatabase;
_existenceState = existenceState;
if (usersContext != null)
{
_usersContextInfo = new DbContextInfo(usersContext);
}
else
{
_usersContextInfo = ((configuration.TargetDatabase == null) ?
new DbContextInfo(configuration.ContextType) :
new DbContextInfo(configuration.ContextType, configuration.TargetDatabase));
if (!_usersContextInfo.IsConstructible)
{
throw Error.ContextNotConstructible(configuration.ContextType);
}
}
// ...
}
,DbMigrator
实例必须是可构造的。现在,让我们看一下DbContextInfo
构造函数。为了使DbContextInfo
具有可构造性,DbContextInfo
和CreateActivator()
均不得返回null。
CreateInstance()
private DbContextInfo(Type contextType, DbProviderInfo modelProviderInfo, AppConfig config, DbConnectionInfo connectionInfo, Func<IDbDependencyResolver> resolver = null)
{
_resolver = (resolver ?? ((Func<IDbDependencyResolver>)(() => DbConfiguration.DependencyResolver)));
_contextType = contextType;
_modelProviderInfo = modelProviderInfo;
_appConfig = config;
_connectionInfo = connectionInfo;
_activator = CreateActivator();
if (_activator != null)
{
DbContext dbContext = CreateInstance();
if (dbContext != null)
{
_isConstructible = true;
using (dbContext)
{
_connectionString = DbInterception.Dispatch.Connection.GetConnectionString(dbContext.InternalContext.Connection, new DbInterceptionContext().WithDbContext(dbContext));
_connectionStringName = dbContext.InternalContext.ConnectionStringName;
_connectionProviderName = dbContext.InternalContext.ProviderName;
_connectionStringOrigin = dbContext.InternalContext.ConnectionStringOrigin;
}
}
}
public virtual bool IsConstructible => _isConstructible;
}
主要搜索DbContext类型或CreateActivator
实现的无参数构造函数,并返回IDbContextFactory<MyContext>
。然后Func<MyContext>
调用该激活器。不幸的是,CreateInstance
构造函数的DbConnectionInfo connectionInfo
参数未被激活器使用,而是仅在创建上下文实例后才应用(为简便起见,删除了不相关的代码):
DbContextInfo
然后,在public virtual DbContext CreateInstance()
{
dbContext = _activator == null ? null : _activator();
dbContext.InternalContext.ApplyContextInfo(this);
return dbContext;
}
内部发生了神奇的事情:连接信息(来自ApplyContextInfo
)在新创建的上下文中被覆盖。
因此,鉴于您必须有一个无参数的构造函数,我的解决方案与您的解决方案相似,但需要进行一些更积极的检查。
_connectionInfo
命令调用默认构造函数,则会抛出该异常。这是我的上下文:
Add-Migration
那我终于可以跑
public class MyContext : DbContext
{
static MyContext()
{
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyContextConfiguration>(useSuppliedContext: true));
}
#if DEBUG
public MyContext()
{
var stackTrace = new System.Diagnostics.StackTrace();
var isMigration = stackTrace.GetFrames()?.Any(e => e.GetMethod().DeclaringType?.Namespace == typeof(System.Data.Entity.Migrations.Design.ToolingFacade).Namespace) ?? false;
if (!isMigration)
throw new InvalidOperationException($"The {GetType().Name} default constructor must be used exclusively for running Add-Migration in the Package Manager Console.");
}
#endif
// ...
}
对于运行迁移,我还没有找到使用Add-Migration -Verbose -ConnectionString "Server=myServer;Database=myDatabase;Integrated Security=SSPI" -ConnectionProviderName "System.Data.SqlClient"
的解决方案,因此,如How do I inject a connection string into an instance of IDbContextFactory?所述,我将DbMigrator
数据库初始化器与MigrateDatabaseToLatestVersion
一起使用。
答案 2 :(得分:1)
创建一个将连接字符串作为Constructur参数的Migrate Initializer,然后将其传递给Migration Constructor,以便它可以使用该连接字符串
public class MigrateInitializer : MigrateDatabaseToLatestVersion<MyContext, Configuration>
{
public MigrateInitializer(string connectionString) : base(true, new Configuration() { TargetDatabase=new System.Data.Entity.Infrastructure.DbConnectionInfo(connectionString,"System.Data.SqlClient") })
{
}
}
将其传递给MigrateInitializer
公共类MyContext:DbContext { 公共MyContext(字符串connectionString) :基础(connectionString) { Database.SetInitializer(new MigrateInitializer(connectionString)); }
}
现在就这样,迁移将使用您提供的连接字符串
答案 3 :(得分:0)
另一个解决方案是迁移到Entity Framework Core。他们已经考虑了这个问题,并且有一个IDesignTimeDbContextFactory.CreateDbContext(string[] args)接口,其中args
是设计时服务提供的参数。
但是请注意,从Entity Framework Core 2.1开始,此功能尚未实现!有关文档,请参见Design-time DbContext Creation,而在GitHub上请参见Tools: Flow arguments into IDesignTimeDbContextFactory,以跟踪进度,并在实现该进度时收到通知。
答案 4 :(得分:0)
扩展答案 https://stackoverflow.com/a/53778826/9941549 :该功能已最终实现,可与 EfCore.Design 5.x 包一起使用。
使用:
IDesignTimeDbContextFactory<YourContext>
的类,YourContext CreateDbContext(string[] args)
,dotnet ef migration add -- this will be passed
)