如何将连接字符串注入到 IDbContextFactory<T> 的实例中? [英] How do I inject a connection string into an instance of IDbContextFactory&lt;T&gt;?

查看:16
本文介绍了如何将连接字符串注入到 IDbContextFactory<T> 的实例中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将 Entity Framework 5 与 Code First 迁移一起使用.我有一个从 DbContext 派生的 DataStore 类:

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
}

还有一个创建 DataStore 类实例的工厂类:

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

这些类在运行时使用 Unity 注入了它们的构造函数参数.到目前为止一切顺利,一切都很好!

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

当我们进行迁移时就会出现问题:因为我的 DataStore 上下文类没有默认构造函数,我需要提供一个 IDbContextFactory 的实现以便 Code First Migrations 可以实例化它:

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

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

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

除此之外,这个类无论如何都不会被Unity实例化;它似乎只是被 Code First Migrations 以某种方式按照惯例调用,所以即使我能做到这一点,它也无济于事......

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.

有人可以帮忙吗?

推荐答案

这是我最终使用的方法,使用来自 这个答案,对我帮助很大.

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

首先,我们向 DataStore 类 (DbContext) 添加另一个不需要连接字符串参数的构造函数:

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

我们的自定义上下文工厂(仅由 Code First Migrations 调用)现在可以继续使用 DataStore 构造函数,该构造函数不需要连接字符串:

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

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

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.

这篇关于如何将连接字符串注入到 IDbContextFactory<T> 的实例中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆