web-dev-qa-db-ja.com

IDbContextFactory <T>のインスタンスに接続文字列を挿入するにはどうすればよいですか?

Code FirstMigrationsでEntityFramework5を使用しています。 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();
    }
}

これらのクラスには、実行時に nity でコンストラクターパラメーターが挿入されます。これまでのところ、すべてがうまく機能しています!

移行を行うと問題が発生します。DataStoreコンテキストクラスにはデフォルトのコンストラクターがないため、Code First Migrationsがインスタンス化できるように、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);
    }
}

実行すると、実行時にMig​​rationsによって次の例外がスローされます。

[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によって慣例で呼び出されているように見えるので、それができたとしても、実際には役に立ちません...

そのメソッドで接続文字列をハードコーディングすればすべて正常に機能しますが、明らかな理由から、それはしたくありません。

誰か助けてもらえますか?

18
Mark Bell

これが私が最終的に使用したアプローチであり、 この回答 からのカスタム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));

それが理にかなっていることを願っています—コメントで説明を求めてください。

5
Mark Bell

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);
18
Polemarch

まず、データベース設定インターフェイスを定義します(例: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>();

あなたの場合、resolveでどこでもそれを使うことができます。

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>();
     .... 
     ....

これで問題が解決することを願っています!ありがとう

1
Bassam Alugili