Как вставить строку подключения в экземпляр IDbContextFactory <T>?
Я использую Entity Framework 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
}
И класс factory, который создает экземпляры класса 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>
, чтобы его можно было преобразовать с помощью First 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);
}
}
Если я это сделаю, я получаю следующее исключение, вызванное Migrations во время выполнения:
[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, поэтому даже если бы я мог это сделать, это не помогло бы...
Все работает отлично, если я жестко закодирую строку соединения в этом методе, но я не хочу этого делать по понятным причинам.
Может ли кто-нибудь помочь?
Ответы
Ответ 1
Здесь подход, который я в конечном итоге использовал, используя пользовательский 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
}
Затем мы делаем то же самое для класса factory:
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();
}
}
Наш пользовательский контекст factory (который только когда-либо вызывается Первыми Миграциями Кодов) теперь может продолжать использовать конструктор 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));
Надеюсь, что все, что имеет смысл, не стесняйтесь просить разъяснений в комментариях.
Ответ 2
Для тех, для кого возможно обновление до 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);
Ответ 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>
Чтобы получить строку подключения из файла Настройки или вашего 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>();
....
....
Я надеюсь, что это решит вашу проблему! спасибо