没有无参数DbContext和DbContextFactory构造函数

时间:2017-02-17 10:59:37

标签: c# entity-framework-6

我的应用程序在我的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的默认构造函数那样的默认连接字符串,它在生产时崩溃了。我想在

上使用相同的基础设施
  • Debug / Relase(并且仅注入不同的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与连接字符串一起使用,那么我该如何提供消费它的DbContextIDbContextFactory<>?而不是无参数构造函数?
      

    添加迁移MyMigration -ConnectionStringName&#34; MyConnectionString&#34;

  • 此处存在同样的问题:我使用的DbMigrator.GetPendingMigrations()也要求无参数DbContextIDbContextFactory<>实施。

据我所知,EntityFramework违反了封装 by implying default constructors和原因 temporal coupling这不是故障安全的。所以请提出一个没有无参数构造函数的解决方案。

5 个答案:

答案 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与连接字符串一起使用,那么如何提供消耗它的DbContextIDbContextFactory<>?代替无参数的构造函数?

花了一些时间对实体框架进行反向工程后,答案是:你做不到!

运行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具有可构造性,DbContextInfoCreateActivator()均不得返回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)在新创建的上下文中被覆盖。

因此,鉴于您必须有一个无参数的构造函数,我的解决方案与您的解决方案相似,但需要进行一些更积极的检查。

  1. 仅在使用 Debug 配置进行编译时才添加默认构造函数。
  2. 如果未从_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 包一起使用。

使用:

  • 在用于运行 ef 工具的项目中,创建实现 IDesignTimeDbContextFactory<YourContext> 的类,
  • 实现方法YourContext CreateDbContext(string[] args)
  • args 将填充双破折号后传递给 ef 工具命令的命令行参数(例如 dotnet ef migration add -- this will be passed
  • EF 只需要该方法返回准备使用的上下文 - 因此您可以随意使用参数(需要连接字符串作为参数、配置路径等)