How do I inject a connection string into an instance of IDbContextFactory<T>?

拈花ヽ惹草 提交于 2019-11-28 07:32:22

For those for whom upgrading to Entity Framework 6 is viable, there's a new overload of the migration initialization that makes this much easier:

    // 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);

Using this, you can run migrations with an injected DbContext as follows:

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

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

Here's the approach I eventually used, using the custom IDatabaseInitializer<T> code from this answer, which helped me out a great deal.

First we add another constructor to the DataStore class (DbContext) which doesn't require the connection string parameter:

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
}

Then we do the same for the factory class:

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

This is the custom initializer code:

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

Our custom context factory (which is only ever called by Code First Migrations) can now carry on using the DataStore constructor which doesn't require a connection string:

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

As long as we set the database initializer to our custom initializer and pass in the connection string (which in my case is done in Global.asax), migrations will use the correct connection:

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

Hope all that makes sense—feel free to ask for clarification in the comments.

First define your database settings interface for example IDBConnectionSettings. In the app.config add the connection string:

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

To retrieve the connection string from your Settings file or your app.config you need for example to do that:

 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;
   }
 }

Now you have to register the Interface somewhere in your code before using it.

unityContainer.Register<IDBConnectionSettings>();

You can use it anywhere with resolve in your case.

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);
    }
}

Update for default constructor

Make a static method or put this code in the default constructor in this way you do not have to give any params.

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

I hope this will solve your problem! thanks

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!