如何将连接字符串注入 IDbContextFactory 的实例中

本文关键字:实例 IDbContextFactory 连接 字符串 注入 | 更新日期: 2023-09-27 18:30:56

我正在使用带有代码优先迁移的实体框架 5。我有一个DataStore类,它派生自DbContext

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<T>的实现,以便代码优先迁移可以实例化它:

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 实例化的;它似乎只是以某种方式被代码优先迁移按照惯例调用,所以即使我可以这样做,它也不会真正帮助......

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

谁能帮忙?

如何将连接字符串注入 IDbContextFactory<T> 的实例中

对于那些可以升级到实体框架 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);
这是我

最终使用的方法,使用这个答案中的自定义IDatabaseInitializer<T>代码,这对我有很大帮助。

首先,我们将另一个构造函数添加到不需要连接字符串参数的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 Migrations 调用)现在可以继续使用不需要连接字符串的 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));

希望这一切都有意义——请随时在评论中要求澄清。

首先定义数据库设置接口,例如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>

若要从设置文件或 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>();
     .... 
     ....

我希望这能解决你的问题!谢谢