问题描述
我的应用程序在 DbContext
实现中没有无参数的构造函数,并且我不喜欢为 IDbContextFactory<>
实现提供无参数的构造函数.
My application has no parameterless constructor at my DbContext
implementation and I don't like to provide a parameterless constructor to a IDbContextFactory<>
implementation.
原因是我想控制DbContext指向的位置.这就是为什么我所有的构造函数都会要求ConnectionStringProvider的原因.
The reason is I want to keep control where the DbContext points to. That's why all my constructors will ask for the 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
的默认构造函数)而在生产环境中崩溃.我想在
I definitely don’t want to add a default constructor! I already did that and it crashed on production because of the wrong connection strings inside the wrong App.config or assuming a default connection string like the default constructor of DbContext
does. I would like use the same infrastructure on
- 调试/发布(并且仅注入其他
IConnectionStringProvider
) - 调用
添加迁移
脚本 - 运行
DbMigrator.GetPendingMigrations()
目前,我收到了一些消息:
Currently I get some of those messages:
---更新---
这可能是我如何将连接字符串注入IDbContextFactory< T>的实例?没有解决方案.我解释了原因:
This might be a duplicate of How do I inject a connection string into an instance of IDbContextFactory<T>? but it has no solution. I explain why:
- 我总是将
Add-Migration
与连接字符串一起使用,那么如何提供使用它的DbContext
或IDbContextFactory<>
?而不是无参数的构造函数?
- I always use
Add-Migration
with connection string, so how can I provide aDbContext
orIDbContextFactory<>
that consumes it? Instead of parameterless constructors?
DbMigrator.GetPendingMigrations()
,它也要求无参数的 DbContext
或 IDbContextFactory<>
实现./li>DbMigrator.GetPendingMigrations()
which also asks for parameterless DbContext
or IDbContextFactory<>
implementations.据我了解,EntityFramework违反了封装暗示默认构造函数,并引起时间耦合,它不是故障安全的.因此,请提出一种无参数构造函数的解决方案.
As far as I understand EntityFramework violates encapsulationby implying default constructors and causestemporal coupling which is not fail-safe. So please propose a solution without parameterless constructors.
推荐答案
花了一些时间对实体框架进行反向工程后,答案是:你做不到!
After spending some time reverse-engineering Entity Framework it turns out the answer is: you can't!
当您运行 Add-Migration
(没有默认构造函数)时,会发生以下情况:
Here's what happens when you run Add-Migration
(with no default constructor):
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
为null, configuration.TargetDatabase
为不 null,并包含信息从命令行参数(例如 -ConnectionStringName
, -ConnectionString
和 -ConnectionProviderName
)传递.因此,将调用 new DbContextInfo(configuration.ContextType,configuration.TargetDatabase)
.
Let's have a look at the DbMigrator
constructor. When run from the Add-Migration
command, usersContext
is null, configuration.TargetDatabase
is not null and contains information passed from the command-line parameters such as -ConnectionStringName
, -ConnectionString
and -ConnectionProviderName
. So new DbContextInfo(configuration.ContextType, configuration.TargetDatabase)
is called.
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()
和 CreateInstance()
都不能返回null.
For the DbMigrator
not to throw, the DbContextInfo
instance must be constructible. Now, let's look at the DbContextInfo
constructor. For the DbContextInfo
to be constructible, both CreateActivator()
and CreateInstance()
must not return 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类型或 IDbContextFactory< MyContext>
实现的无参数构造函数,并返回 Func< MyContext>
.然后 CreateInstance
调用该激活器.不幸的是,激活器没有使用 DbContextInfo
构造函数的 DbConnectionInfo connectionInfo
参数,而是仅在创建上下文实例之后才应用(为简便起见,删除了无关的代码):
CreateActivator
basically searches for a parameterless constructor of either your DbContext type or your IDbContextFactory<MyContext>
implementation and returns a Func<MyContext>
. Then CreateInstance
calls that activator. Unfortunately, the DbConnectionInfo connectionInfo
parameter of the DbContextInfo
constructor is not used by the activator but is only applied later after the context instance is created (irrelevant code removed for brevity):
public virtual DbContext CreateInstance()
{
dbContext = _activator == null ? null : _activator();
dbContext.InternalContext.ApplyContextInfo(this);
return dbContext;
}
然后,在 ApplyContextInfo
内部,魔术发生了:连接信息(来自 _connectionInfo
)在新创建的上下文中被覆盖.
Then, inside ApplyContextInfo
, the magic happens: the connection info (from _connectionInfo
) is overridden on the newly created context.
因此,鉴于您必须具有无参数的构造函数,我的解决方案与您的解决方案相似,但需要进行一些更积极的检查.
So, given that you must have a parameterless constructor, my solution is similar to yours, but with a few more aggressive checks.
- 仅在 Debug 配置中进行编译时才添加默认构造函数.
- 如果未从
Add-Migration
命令调用,则默认构造函数将抛出该异常.
- The default constructor is only added when compiling in Debug configuration.
- The default constructor throws if not called from the
Add-Migration
command.
这是我的上下文:
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的实例中?.
And for running the migrations I haven't found a solution using a DbMigrator
explicitly, so I use the MigrateDatabaseToLatestVersion
database initializer with useSuppliedContext: true
as explained in How do I inject a connection string into an instance of IDbContextFactory? .
这篇关于没有无参数DbContext和DbContextFactory构造函数的Add-Migration的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!