5

I hope somebody is able to help me, because it seems I'm totally stuck.

For upcoming projects in our company we'd like to use Entity Framework 5 with an code first approach. I played around a little while and everytime I try to use EF with our existing libraries, I fail because it seems EF heavily relies on an existing app.config.

In our company, we have an inhouse database library that allows us to connect to various data sources and database technologies taking the advantages of MEF (managed extensibility framework) for database providers. I just have to pass some database settings, such as host (or file), catalog, user credentials and a database provider name, the library looks for the appropriate plugin and returns me a custom connection string or IDbConnection. We'd like to use this library together with EF because it allows us to be flexible about which database we use also change the database at runtime.

So. I saw that a typical DbContext object takes no parameters in the constructor. It automatically looks for the appropriate connection string in app.config. We don't like such things so I changed the default constructor to take a DbConnection object that get's passed to the DbContext base class. No deal.

Problems occur when the code first model changes. EF automatically notices this and looks for migration classes / configuration. But: A typical migration class requires a default parameterless constructor for the context! What a pity!

So we build our own migration class using the IDbContextFactory interface. But again, it seems that also this IDbContextFactory needs a parameterless constructor, otherwise I'm not able to add migrations or update the database.

Further, I made my own data migration configurator where I pass the context, also the target database. Problem is here: It doesn't find any migration classes, no matter what I try.

I'm completely stuck because it seems the only way to use EF is when connection strings are saved in app.config. And this is stupid because we need to change database connections at runtime, and app.config is read-only for default users!

How to solve this?

Atrotygma
  • 1,133
  • 3
  • 17
  • 31
  • If you don't pass in the connectionstring then it must read it from the app.config? Otherwise, how would it ever find it? – Sam Leach Apr 25 '13 at 09:30

2 Answers2

3

The answer is provided here

https://stackoverflow.com/a/15919627/941240

The trick is to slightly modify the default MigrateDatabaseToLatestVersion initializer so that:

  • the database is always initialized ...
  • ... using the connection string from current context

The DbMigrator will still create a new data context but will copy the connection string from yours context according to the initializer. I was even able to shorten the code.

And here it goes:

public class MasterDetailContext : DbContext
{
    public DbSet<Detail> Detail { get; set; }
    public DbSet<Master> Master { get; set; }

    // this one is used by DbMigrator - I am NOT going to use it in my code
    public MasterDetailContext()
    {
        Database.Initialize( true );
    }

    // rather - I am going to use this, I want dynamic connection strings
    public MasterDetailContext( string ConnectionString ) : base( ConnectionString )
    {
        Database.SetInitializer( new CustomInitializer() );
        Database.Initialize( true );
    }

    protected override void  OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }
}

public class CustomInitializer : IDatabaseInitializer<MasterDetailContext>
{

    #region IDatabaseInitializer<MasterDetailContext> Members

    // fix the problem with MigrateDatabaseToLatestVersion 
    // by copying the connection string FROM the context
    public void InitializeDatabase( MasterDetailContext context )
    {            
        Configuration cfg = new Configuration(); // migration configuration class
        cfg.TargetDatabase = new DbConnectionInfo( context.Database.Connection.ConnectionString, "System.Data.SqlClient" );

        DbMigrator dbMigrator = new DbMigrator( cfg );
        // this will call the parameterless constructor of the datacontext
        // but the connection string from above will be then set on in
        dbMigrator.Update();             
    }

    #endregion
}

Client code:

    static void Main( string[] args )
    {

        using ( MasterDetailContext ctx = new MasterDetailContext( @"Database=ConsoleApplication801;Server=.\SQL2012;Integrated Security=true" ) )
        {
        }

        using ( MasterDetailContext ctx = new MasterDetailContext( @"Database=ConsoleApplication802;Server=.\SQL2012;Integrated Security=true" ) )
        {
        }
    }

Running this will cause the two databases to be created and migrated according to the migration configuration.

Community
  • 1
  • 1
Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
  • Beautiful solution that solved my problem perfectly. It's horrible that I have to do this everytime for each context - it doesn't seem Microsoft thought about dynamic database connections and such. – Atrotygma Apr 30 '13 at 10:08
  • I remember spending 3 or 4 hours trying to figure that out. Nice thing, however, is that when you finally have the actual solution, it seems quite simple. – Wiktor Zychla Apr 30 '13 at 10:46
  • I know this answer has been around for a while, but I'm getting a System.StackOverflowException when I implement it. It seems that when constructing a new DbMigrator in the InitializeDatabase method invokes the default constructor of the MasterDetailContext (as per the comments) and the call to `Database.Initialize( true );` in the default constructor somehow invokes InitializeDatabase again so it gets stuck in an infinite loop. Am I missing something? – Kerby Jun 18 '14 at 00:06
  • Do not call the initialize in the constructor. You need the initialization only once, not everytime a new context is created. I will take a look if I made any modifications at my side since I provider this answer. – Wiktor Zychla Jun 18 '14 at 06:05
0

It needs a parameterless constructor in order to invoke it. What you could do is provide your default DbConntectionFactory in the empty constructor, something like:

public DbContext()
{
    IDbContextFactory defaultFactory; //initialize your default here
    DbContext(defaultFactory);
}

public DbContext(IDbContextFactory factory)
{
}
Mathew Thompson
  • 55,877
  • 15
  • 127
  • 148