Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
153 views
in Technique[技术] by (71.8m points)

c# - 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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...