Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Code first custom connection string and migrations without using IDbContextFactory

I am trying to write an easy to understand DBContext class that takes a custom connection string, can run migrations, and that I allows me to generate migrations using Package Manager.

I seem to be going around in circles.

I have been able to get it working using code that feels awful to me. I documented this in my answer to This question on connection string and migrations.

Radek's answer looks much better than mine, but I find that when I implement it and then try and create a migration in Package Manager I get the message

The target context 'DataLayer.Context' is not constructible. Add a default constructor or provide an implementation of IDbContextFactory.

Where DataLayer.Context is my context class.

I don't want to provide an implementation of IDbContextFactory ( and Radek's answer seems to indicate it isn't needed )

UPDATE:

I can generate a migration if I include a constructor with no parameter. For example

public Context() : base("ConnectionStringName") { }

For my context creation I pass the name of the connection string in app.config

public Context(string connString) : base(connString)
{
    Database.SetInitializer(new CustomInitializer());
    Database.Initialize(true);
}

At last I am able to both generate migrations, and run migrations for databases that the user selects.

HOWEVER: When I drop the database and then run my app I have problems.

The initialiser code I am using, from the link above is

public class CustomInitializer : IDatabaseInitializer<Context>
{       
    public void InitializeDatabase(Context context)
    {
        try
        {
            if (!context.Database.Exists())
            {
                context.Database.Create();
            }
            else
            {
                if (!context.Database.CompatibleWithModel(false))  
                {
                    var configuration = new Configuration();
                    var migrator = new DbMigrator(configuration);
                    migrator.Configuration.TargetDatabase =
                        new DbConnectionInfo(context.Database.Connection.ConnectionString);
                    IEnumerable<string> migrations = migrator.GetPendingMigrations();
                    foreach (string migration in migrations)
                    {
                        var scriptor = new MigratorScriptingDecorator(migrator);
                        string script = scriptor.ScriptUpdate(null, migration);
                        context.Database.ExecuteSqlCommand(script);
                    }
                }
            }
        }
        catch (Exception ex)
        {
        }
    }       
}

When I drop the database a new one gets created but it has no tables. That would be because my table creation code is all in my first migration.

So the code inside the !context.Database.CompatibleWithModel(false) condition does not run.

However alas, the code also does not run the second time around when it should have the metadatamodel.

Now to try and get the migration running...

SADNESS: No with Radek's custom initializer so far.

From NSGaga's comments I have resorted to exiting the application if I change connection.

var master = new myMDIForm();
master.ConnectionType = connectionType;   // being an enum of the different connection names in app.config
while (master.ConnectionType != ConnectionType.None )
{
   Application.Run(master);
}
like image 810
Kirsten Avatar asked Apr 10 '13 13:04

Kirsten


1 Answers

(note: I have no idea about your custom initializer, I just saw that - this is a general solution to the connection caching problem and migration initializer - but should work with variations)


This is what works for me - and based on the buggy behavior that I described here.
static string _connection;
public MyContext()
    : base(_connection ?? "DefaultConection")
{
    Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyNamespace.Migrations.Configuration>());
}
public MyContext(string connection)
    : base(connection)
{
    _connection = connection;
    Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyNamespace.Migrations.Configuration>());
}

Few points:

1) Define static connection in your context - and as you set a 'new one' change it to hold the latest value. That helps keeping things in synchronization - no matter who accesses the DbContext has the same one (it's similar in concept to Factory just easier on the eyes,

2) Problem with Migrations is - that MigrateDatabaseToLatestVersion caches the connection (and entire configuration internally) in a readonly field internally - and even if you change it in your DbContext, or whatever you set outside, it gets 'out of sync`.

There is no way getting around that but to make a 'new initializer'.

3) What @Radek discovered was actually the solution for that problem - and in line with (2). I just removed the Initialize(true) as it's unnecessary - that gets called when 'the time is right'.


That's about it.

With this I can now flip my connections around the clock - and as I want.
(meaning I can change connection at runtime - migrate/create two, or more, databases and keep changing connections and working on them simultaneously)

And this is the code I actually use to cycle between connections...

for (var flip = true; true; flip = !flip)
{
    using (var db = new MyContext(flip ? "Name=DefaultConnection" : "Name=OtherConnection"))
    {
        // usual db code
    }
}

The key is to set initializer each time you set the connection.
The old one is still around - and the old connection (you can't delete the Db while app is running)

like image 111
NSGaga-mostly-inactive Avatar answered Nov 06 '22 01:11

NSGaga-mostly-inactive