Mark Bell Mark Bell - 26 days ago 18
C# Question

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

I'm using Entity Framework 5 with Code First Migrations. I have a

DataStore
class which derives from
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
}


And a factory class which creates instances of the
DataStore
class:

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


These classes have their constructor parameters injected at runtime with Unity. So far so good, everything works great!

The problem arises when we get to migrations: because my
DataStore
context class doesn't have a default constructor, I need to supply an implementation of
IDbContextFactory<T>
so that Code First Migrations can instantiate it:

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


The issue is that I can't figure out how I can inject the connection string into this class. I can't create a new constructor with a connection string parameter like this:

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


If I do, I get the following exception thrown by Migrations at runtime:

[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


Aside from that, this class is not instantiated by Unity anyway; it seems to just be called by convention by Code First Migrations somehow, so even if I could do that it wouldn't really help...

Everything works fine if I hard-code the connection string in that method, but I don't want to do that, for obvious reasons.

Can anyone help please?

Answer

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.