如何将连接字符串注入 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 实例化的;它似乎只是以某种方式被代码优先迁移按照惯例调用,所以即使我可以这样做,它也不会真正帮助......
如果我在该方法中对连接字符串进行硬编码,一切正常,但出于显而易见的原因,我不想这样做。
谁能帮忙?
对于那些可以升级到实体框架 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>();
....
....
我希望这能解决你的问题!谢谢