如何将连接字符串注入IDbContextFactory <T>的实例?

Mar*_*ell 18 c# entity-framework dependency-injection unity-container asp.net-mvc-4

我正在使用Entity Framework 5和Code First Migrations.我有一个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
}
Run Code Online (Sandbox Code Playgroud)

还有一个创建类实例的工厂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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这些类的构造函数参数在运行时使用Unity注入.到目前为止一切都那么好,一切都很棒!

当我们进行迁移时会出现问题:因为我的DataStore上下文类没有默认构造函数,所以我需要提供一个实现,IDbContextFactory<T>以便Code 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"); 
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是我无法弄清楚如何将连接字符串注入此类.我不能用这样的连接字符串参数创建一个新的构造函数:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我这样做,我会在运行时获得以下异常抛出的异常:

[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
Run Code Online (Sandbox Code Playgroud)

除此之外,这个类还没有被Unity实例化; 似乎只是按照惯例通过Code First Migrations以某种方式调用,所以即使我能做到这一点也不会真的有帮助......

如果我在该方法中对连接字符串进行硬编码,一切正常,但出于显而易见的原因,我不想这样做.

有人可以帮忙吗?

Pol*_*rch 18

对于那些升级到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);
Run Code Online (Sandbox Code Playgroud)

使用此方法,您可以使用注入的DbContext运行迁移,如下所示:

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

using (var context = kernel.Get<MyDbContext>())
    context.Database.Initialize(false);
Run Code Online (Sandbox Code Playgroud)

  • 这真的很有帮助,谢谢!:)应该被接受为正确答案... (2认同)

Mar*_*ell 5

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
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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); 
    }
}
Run Code Online (Sandbox Code Playgroud)

只要我们将数据库初始化程序设置为我们的自定义初始化程序并传入连接字符串(在我的情况下完成Global.asax),迁移将使用正确的连接:

Database.SetInitializer<DataStore>(new MigrateDatabaseToLatestVersionWithConnectionString<DataStore, MyMigrationsConfiguration>(INJECTED_CONNECTION_STRING_HERE));
Run Code Online (Sandbox Code Playgroud)

希望所有有意义的事情 - 随意在评论中要求澄清.