4

I am trying to write a method to create a database and run migrations on it, given the connection string.

I need the multiple connections because I record an audit log in a separate database. I get the connection strings out of app.config using code like

ConfigurationManager.ConnectionStrings["Master"].ConnectionString;

The code works with the first connection string defined in my app.config but not others, which leads me to think that somehow it is getting the connection string from app.config in some manner I don't know.

My code to create the database if it does not exist is

private static Context MyCreateContext(string ConnectionString)
  {
   // put the connection string where the factory method can get it
   AppDomain.CurrentDomain.SetData("ConnectionString", ConnectionString );
   var factory = new ContextFactory();
   // I know I need this line - but I cant see how what follows actually uses it
   Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context,DataLayer.Migrations.Configuration>());
   var context = factory.Create();
   context.Database.CreateIfNotExists(); 
   return context
   }

The code in the Migrations.Configuration is

Public sealed class Configuration :  DbMigrationsConfiguration<DataLayer.Context>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }
}

The context factory code is

  public class ContextFactory : IDbContextFactory<Context>
{
    public Context Create()
    {
        var s = (string)AppDomain.CurrentDomain.GetData("ConnectionString");

        return new Context(s);
    }
}

Thus I am setting the connection string before creating the context.
Where can I be going wrong, given that the connection strings are all the same except the database name, and the migration code runs with one connection string, but doesnt run with others?

I wonder if my problem is to do with understanding how How does Database.SetInitializer actually works. I am guessing something about reflection or generics. How do i make the call to SetInitializer tie tie to my actual context?

I have tried the following code but the migrations do not run

 private static Context MyCreateContext(string ConnectionString)
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context, DataLayer.Migrations.Configuration>());
        var context = new Context(ConnectionString);
        context.Database.CreateIfNotExists();
    }

This question appears to be related

UPDATE:

I can get the migrations working if I refer to the connection string using public MyContext() : base("MyContextConnection") - which points to in the config

I was also able to get migrations working on using different instances of the context, if I created a ContextFactory class and passed the connection to it by referencing a global. ( See my answer to the related question link )

Now I am wondering why it has to be so hard.

Community
  • 1
  • 1
Kirsten
  • 15,730
  • 41
  • 179
  • 318

2 Answers2

7

I'm not sure exactly as to what the problems are you facing, but let me try

The easiest way to provide connection - and be sure it works that way...

1) Use your 'DbContext' class name - and define a connection in the app.config (or web.config). That's easiest, you should have a connection there that matches your context class name,

2) If you put it into the DbContext via constructor - then be consistent and use that one. I'd also suggest to 'read' from config connections - and again name it 'the same' as your context class (use the connection 'name', not the actual string),

3) if none is present - EF/CF makes the 'default' one - based on your provider - and your context's class name - which usually isn't what you want,

You shouldn't customize with initializers for that reason - initializers should be agnostic and serve other purpose - setup connection in the .config - or directly on your DbContext

Also check this Entity Framework Code First - How do I tell my app to NOW use the production database once development is complete instead of creating a local db?

Always check 'where your data' goes - before doing anything.

For how the initializer actually works - check this other post of mine, I made a thorough example

How to create initializer to create and migrate mysql database?

Notes: (from the comments)

Connection shouldn't be very dynamic - config is the right place for it to be, unless you have a good reason.
Constructor should work fine too.
CreateDbIfNotExists doesn't work well together with the 'migration' initializer. You can just use the MigrateDatabaseToLatestVersion initializer. Don't 'mix' it

Or - put something like public MyContext() : base("MyContextConnection") - which points to <connectionStrings> in the config

To point to connection - just use its 'name' and put that into constructor.

Or use somehting like ConfigurationManager.ConnectionStrings["CommentsContext"].ConnectionString

Regarding entertaining 'multiple databases' with migrations (local and remote from one app) - not exactly related - but this link - Migration not working as I wish... Asp.net EntityFramework

Update: (further discussion here - Is adding a class that inherits from something a violation of the solid principles if it changes the behavior of code?)

It is getting interesting here. I did manage to reproduce the problems you're facing actually. Here is a short breakdown on what I think it's happening:

First, this worked 'happily':

Database.SetInitializer(new CreateAndMigrateDatabaseInitializer<MyContext, MyProject.Migrations.Configuration>());
for (var flip = false; true; flip = !flip)
{
    using (var db = new MyContext(flip ? "Name=MyContext" : "Name=OtherContext"))
    {
        // insert some records...
        db.SaveChanges();
    }
}

(I used custom initializer from my other post, which controls migration/creation 'manually')

That worked fine w/o an Initializer. Once I switched that on, I ran into some curious problems.

I deleted Db-s (two, for each connection). I expected to either not work, or create one db, then another in the next pass (like it did, w/o migrations, just 'Create' initializer).

What happened, to my surprise - is it actually created both databases on the first pass ??

Then, being a curious person:), I put breakpoints on the MyContext ctor, and debugged through the migrator/initializer. Again empty/no db-s etc.

It created first instance on my call within the flip. Then on the first access to 'model', it invoked the initializer. Migrator took over (having had no db-s). During the migrator.Update(); it actually constructs the MyContext (I'm guessing via generic param in Configuration) - and calls the 'default' empty ctor. That had the 'other connection/name' by default - and creates the other Db all as well.

So, I think this explains what you're experiencing. And why you had to create the 'Factory' to support the Context creation. That seems to be the only way. And setting some 'AppDomain' wide 'connection string' (which you did well actually) which isn't 'overriden' by default ctor call.

Solution that I see is - you just need to run everything through factory - and 'flip' connections in there (no need for static connection, as long as your factory is a singleton.

Community
  • 1
  • 1
NSGaga-mostly-inactive
  • 14,052
  • 3
  • 41
  • 51
  • My problem is i cant get the migrations to run when using anything but the app.config connection string. I would like to be able to call the code to run the migrations directly... looking at your example now – Kirsten Apr 06 '13 at 00:56
  • I have 3 connection strings. Actually they are defined in the app.config. But I query the app.config to get them so I can use them explicitly. The problem is that only the first works. They are all the same SQL instance with a different database name. I have edited the question to try and make it clearer – Kirsten Apr 06 '13 at 01:02
  • I just want a method that creates the database if needed and applies migrations if needed. I want this to happen explicitly so I feel it is under my control - rather than setting up an event to fire off the first time the code is accessed... is this old fashioned? – Kirsten Apr 06 '13 at 01:17
  • you'll get 'out of control' that way :) yes, a bit. You have to work the 'code first/migrations' way. Set initializer and let it call you. Don't mix - I'd just use the 'migrations' initializer (you cannot use both, but you can 'customize' like you did - and there are problems, I tried). Migration has to be first - but create doesn't make sense afterwards - look up that link of mine. I'm still not sure 'why'? I need a 'why' to be able to tell you 'how' - give me a good 'use case' and what you want to achieve - so far I haven't seen one. – NSGaga-mostly-inactive Apr 06 '13 at 01:24
  • Use Case: I have a local and remote database, which should have the same structure. The remote database is a mirror of the local, but read only. I want the user be able to switch connection within the app. For testing purposes I do want the remote database structure to update if there are pending migrations. Let it call me - How can I make sure it does? My problem is that if I let it call me it doesnt. – Kirsten Apr 06 '13 at 01:37
  • I need the local and the remote database to create if missing and apply any pending migrations – Kirsten Apr 06 '13 at 01:49
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/27683/discussion-between-kirsten-g-and-nsgaga) – Kirsten Apr 06 '13 at 05:41
  • I just revised (back) on my previous comments - you indeed can have multiple connections - as long as the Db is the same (and created using the same migration (then just 'updated') I managed to switch in between and populate different data. But no extra code just created context with different config name (both connections were in the .config - so I just give the names). I'm really not sure as to why you're experiencing problems. I had to check the source / EF to see what's going on. There is a singleton part of it (tied to configuration - unless different context?), but connection is dynamic – NSGaga-mostly-inactive Apr 07 '13 at 00:09
  • This is not a multi-tenanted solution. I am switching between different Dbs that should have the same structure. My problem was that I couldn't get the migrations running in any but the default database. Solved now with a very weird solution of introducing another class. I aren't happy with this weird solution and have asked a further question [here] (http://stackoverflow.com/questions/15856703/is-adding-a-class-that-inherits-from-something-a-violation-of-the-solid-principl) – Kirsten Apr 07 '13 at 01:07
  • I'm just writing my update here - I know what's going on :) - but since you made a new question I'll put it in there as well, this is getting too long anyway. – NSGaga-mostly-inactive Apr 07 '13 at 01:13
0

You can supply a configuration in the MigrateDatabaseToLatestVersion constructor. If you set the initializer in the DbContext you can also pass a 'true' to use the current connection string.

Martien de Jong
  • 731
  • 1
  • 7
  • 19