我总是用Add-Migration
带有连接字符串,那么我如何提供DbContext
or 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
is notnull 并包含从命令行参数传递的信息,例如-ConnectionStringName
, -ConnectionString
and -ConnectionProviderName
. So 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);
}
}
// ...
}
For the DbMigrator
不扔,DbContextInfo
实例必须是可构造的。现在,让我们看看DbContextInfo
构造函数。为了DbContextInfo
是可建造的,两者CreateActivator()
and CreateInstance()
不得返回 null。
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;
}
CreateActivator
基本上搜索 DbContext 类型或您的 DbContext 类型的无参数构造函数IDbContextFactory<MyContext>
执行并返回一个Func<MyContext>
. Then CreateInstance
调用该激活器。不幸的是,DbConnectionInfo connectionInfo
的参数DbContextInfo
激活器不使用构造函数,但仅在创建上下文实例后才应用(为简洁起见,删除了不相关的代码):
public virtual DbContext CreateInstance()
{
dbContext = _activator == null ? null : _activator();
dbContext.InternalContext.ApplyContextInfo(this);
return dbContext;
}
然后,里面ApplyContextInfo
,神奇的事情发生了:连接信息(来自_connectionInfo
) 在新创建的上下文中被覆盖。
所以,鉴于你must有一个无参数构造函数,我的解决方案与您的类似,但有一些更积极的检查。
- 默认构造函数仅在编译时添加Debug配置。
- 如果没有从以下位置调用,默认构造函数将抛出异常
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"
为了运行迁移,我还没有找到使用的解决方案DbMigrator
明确地,所以我使用MigrateDatabaseToLatestVersion
数据库初始值设定项useSuppliedContext: true
如中所解释的如何将连接字符串注入 IDbContextFactory 的实例中? https://stackoverflow.com/questions/18734496/how-do-i-inject-a-connection-string-into-an-instance-of-idbcontextfactoryt/32478553#32478553 .