【问题标题】:How do I inject a connection string into an instance of IDbContextFactory<T>?如何将连接字符串注入 IDbContextFactory<T> 的实例?
【发布时间】:2013-09-15 01:58:57
【问题描述】:

我正在使用 Entity Framework 5 和 Code First 迁移。我有一个派生自DbContextDataStore 类:

public class DataStore : DbContext, IDataStore
{
    public int UserID { get; private set; }

    public DataStore(int userId, string connectionString) : base(connectionString)
    {
        UserID = userId;
    }

    public virtual IDbSet<User> Users { get; set; }

    // Rest of code here
}

还有一个创建DataStore 类实例的工厂类:

public class DataStoreFactory : Disposable, IDataStoreFactory
{
    private DataStore _database;
    private int _userId;
    private string _connectionString;

    public DataStoreFactory(int userId, string connectionString)
    {
        _userId = userId;
        _connectionString = connectionString;
    }

    public IDataStore Get()
    {
        _database = new DataStore(_userId, _connectionString);
        return _database;
    }

    protected override void DisposeCore()
    {
        if (_database != null) _database.Dispose();
    }
}

这些类的构造函数参数在运行时注入Unity。到目前为止一切顺利,一切正常!

当我们开始迁移时,问题就出现了:因为我的DataStore 上下文类没有默认构造函数,我需要提供IDbContextFactory&lt;T&gt; 的实现,以便 Code First Migrations 可以实例化它:

public class MigrationDataStoreFactory : IDbContextFactory<DataStore>
{
    public DataStore Create()
    {
        // Need to inject connection string so we can pass it to this constructor
        return new DataStore(0, "CONNECTION_STRING_NEEDED_HERE"); 
    }
}

问题是我不知道如何将连接字符串注入这个类。我不能用这样的连接字符串参数创建一个新的构造函数:

public class MigrationDataStoreFactory : IDbContextFactory<DataStore>
{
    public string _connectionString { get; set; }

    public MigrationDataStoreFactory(string connectionString)
    {
        _connectionString = connectionString;
    }

    public DataStore Create()
    {
        return new DataStore(0, new DateTimeProvider(() => DateTime.Now), _connectionString);
    }
}

如果这样做,我会在运行时收到迁移引发的以下异常:

[InvalidOperationException: The context factory type 'MigrationDataStoreFactory' must have a public default constructor.]
    System.Data.Entity.Infrastructure.DbContextInfo.CreateActivator() +326
    System.Data.Entity.Infrastructure.DbContextInfo..ctor(Type contextType, DbProviderInfo modelProviderInfo, AppConfig config,     DbConnectionInfo connectionInfo) +106
    System.Data.Entity.Infrastructure.DbContextInfo..ctor(Type contextType) +52
    System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration, DbContext usersContext) +202
    System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration) +66
    System.Data.Entity.MigrateDatabaseToLatestVersion`2.InitializeDatabase(TContext context) +50
    // Truncated stack trace, but you get the idea

除此之外,Unity 无论如何都不会实例化此类;它似乎只是被 Code First Migrations 按照惯例以某种方式调用,所以即使我能做到这一点也无济于事......

如果我在该方法中对连接字符串进行硬编码,一切正常,但出于显而易见的原因,我不想这样做。

有人可以帮忙吗?

【问题讨论】:

  • 也许我完全误解了你的问题,但你不能只创建一个从 appconfig 读取连接字符串的包装类(例如 IDbConnectionConfig/DbConnectionConfig)并使用 Unity/inject 注册它吗?
  • 是的,我可以,但我会将它注入什么? ;) 连接字符串已经使用 Unity 注入 - 问题是我不知道如何将 anything 注入 MigrationDataStoreFactory
  • “上下文...默认构造函数”异常来自哪里?是 Unity 引发了该异常还是 EF 迁移?您可以使用堆栈跟踪更新您的问题吗?
  • 为什么你不尝试使用 IDbConnectionFactory 而不是到处都提供连接字符串!
  • 嗨@Steven,这是引发错误的迁移。我现在已经更新了这个问题,希望能稍微澄清一下,但正如我在编辑中所说,我不确定即使我可以创建一个新的构造函数它是否会有所帮助。

标签: c# entity-framework asp.net-mvc-4 dependency-injection unity-container


【解决方案1】:

对于那些可以升级到 Entity Framework 6 的人来说,迁移初始化的新重载使这变得更加容易:

    // Parameters:
    //   useSuppliedContext:
    //     If set to true the initializer is run using the connection information from the
    //     context that triggered initialization. Otherwise, the connection information
    //     will be taken from a context constructed using the default constructor or registered
    //     factory if applicable.
    public MigrateDatabaseToLatestVersion(bool useSuppliedContext);

使用它,您可以使用注入的 DbContext 运行迁移,如下所示:

Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, MyMigrationConfiguration>(useSuppliedContext: true));

using (var context = kernel.Get<MyDbContext>())
    context.Database.Initialize(false);

【讨论】:

  • 这真的很有帮助,谢谢! :) 应该被接受为正确答案...
  • 这确实救了我几个小时的头痛。
  • 这是纯金
【解决方案2】:

这是我最终使用的方法,使用来自 this answer 的自定义 IDatabaseInitializer&lt;T&gt; 代码,这对我有很大帮助。

首先我们向 DataStore 类 (DbContext) 添加另一个不需要连接字符串参数的构造函数:

public class DataStore : DbContext, IDataStore
{
    public int UserID { get; private set; }

    // This is the constructor that will be called by the factory class 
    // if it is initialised without a connection string parameter
    public DataStore(int userId)
    {
        UserID = userId;
    }

    public DataStore(int userId, string connectionString) : base(connectionString)
    {
        UserID = userId;
    }

    public virtual IDbSet<User> Users { get; set; }

    // Rest of code here
}

然后我们对工厂类做同样的事情:

public class DataStoreFactory : Disposable, IDataStoreFactory
{
    private DataStore _database;
    private int _userId;
    private string _connectionString;

    // This is the constructor that will be called by the 
    // MigrationDataStoreFactory class
    public DataStoreFactory(int userId)
    {
        _userId = userId;
    }

    public DataStoreFactory(int userId, string connectionString)
    {
        _userId = userId;
        _connectionString = connectionString;
    }

    public IDataStore Get()
    {
        // If we have a connection string, construct our context with it,
        // if not, use the new constructor
        if(_connectionString != null)
            _database = new DataStore(_userId, _dateTimeServices, _connectionString);
        else
            _database = new DataStore(_userId, _dateTimeServices);

        return _database;
    }

    protected override void DisposeCore()
    {
        if (_database != null) _database.Dispose();
    }
}

这是自定义初始化代码:

public class MigrateDatabaseToLatestVersionWithConnectionString<TContext, TMigrationsConfiguration> : IDatabaseInitializer<TContext>
    where TContext : DbContext
    where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
{
    private readonly DbMigrationsConfiguration _config;

    public MigrateDatabaseToLatestVersionWithConnectionString()
    {
        _config = new TMigrationsConfiguration();
    }

    public MigrateDatabaseToLatestVersionWithConnectionString(string connectionString)
    {
        // Set the TargetDatabase for migrations to use the supplied connection string
        _config = new TMigrationsConfiguration { 
            TargetDatabase = new DbConnectionInfo(connectionString, 
                                                  "System.Data.SqlClient")
        };
    }

    public void InitializeDatabase(TContext context)
    {
        // Update the migrator with the config containing the right connection string
        DbMigrator dbMigrator = new DbMigrator(_config);
        dbMigrator.Update();
    }
}

我们的自定义上下文工厂(仅由 Code First 迁移调用)现在可以继续使用 DataStore 构造函数,不需要连接字符串:

public class MigrationDataStoreFactory : IDbContextFactory<DataStore>
{
    public DataStore Create()
    {
        return new DataStore(0); 
    }
}

只要我们将数据库初始化程序设置为自定义初始化程序并传入连接字符串(在我的例子中是在 Global.asax 中完成的),迁移将使用正确的连接:

Database.SetInitializer<DataStore>(new MigrateDatabaseToLatestVersionWithConnectionString<DataStore, MyMigrationsConfiguration>(INJECTED_CONNECTION_STRING_HERE));

希望所有这些都是有道理的——请随时在 cmets 中寻求澄清。

【讨论】:

  • MigrationDataStoreFactory.Create() 方法中从 Unity 静态解析连接字符串不是更简单吗?那么你就不需要使用你自己的初始化程序了。你不会注入,但它会更简单,更容易维护。我目前看不到可以注射。
  • 这目前正在一个实时网络应用程序中工作,所以是的,这是可能的!至于维护:自从我写了这段代码以来,我没有再碰过它,所以我不认为这是一个问题。我最初想避免静态注入的主要原因是因为它会在我的代码中引入对 Unity 本身的依赖;但事实证明,这将是唯一的例子,所以如果我想在某个时候换掉 DI 容器,也不会太费力。也许是我过度架构的一个案例,我会告诉你的。 :)
  • @nicodemus13 究竟该怎么做呢?我找不到任何关于如何在没有构造函数注入的情况下使用 Unity。
  • 这似乎是维护的一场噩梦。必须为每种上下文类型创建一个数据库初始化程序...必须修改我的构造函数以不使用连接字符串,即使这是它们的一部分合同...肯定有更好的方法-但我似乎找不到。 2018 年有什么更好的吗?
【解决方案3】:

首先定义您的数据库设置界面,例如IDBConnectionSettings。 在app.config 中添加连接字符串:

  <connectionStrings>
    <add name=" ConnectionString "
      connectionString="Integrated Security=SSPI; Persist Security Info=False;   InitialCatalog=DB; Data Source=(local);"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

要从您的 Settings 文件或 app.config 中检索连接字符串,您需要例如这样做:

 public class DBConnectionSettings()
 {
   get ConnectionString
   {
       var connections = ConfigurationManager.ConnectionStrings;
       // From app.config you will get the connection string
       var connectionString = connections["ConnectionString"].ConnectionString;

     return connectionString;
   }
 }

现在您必须在代码中的某处注册接口,然后才能使用它。

unityContainer.Register<IDBConnectionSettings>();

你可以在任何地方使用它来解决你的情况。

public class MigrationDataStoreFactory : IDbContextFactory<DataStore>
{
    public string _connectionString { get; set; }

    public MigrationDataStoreFactory(UnityContainer unityContainer)
    {
        _connectionString = unityContainer.Resolve<IDBConnectionSettings>().ConnectionString;
    }

    public DataStore Create()
    {
        return new DataStore(0, new DateTimeProvider(() => DateTime.Now), _connectionString);
    }
}

更新默认构造函数

创建一个静态方法或将此代码放在默认构造函数中,这样您就不必提供任何参数。

  var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = Application.StartupPath + Path.DirectorySeparatorChar + @"app.config" }; // application name must be

  using (var unityContainer = new UnityContainer())
  {
    var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
    var unitySection = (UnityConfigurationSection)configuration.GetSection("unity");

    unityContainer.LoadConfiguration(unitySection, "ConnectionString");
    {
     unityContainer.Resolve<IDBConnectionSettings>();
     .... 
     ....

希望这能解决您的问题!谢谢

【讨论】:

  • 如果您不想在构造函数中添加unityContainer,您可以使用无参数构造函数并使用静态解析器解决它!这种方法会替换类中的所有连接字符串。
  • 正如我所说,不可能在最终代码示例中执行您正在执行的操作,即向构造函数中注入一些东西——这会引发我在问题中提到的异常。但是,您提到了一个静态解析器:请问它是如何工作的?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-06
  • 2011-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-20
相关资源
最近更新 更多