Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusion over EF Auto Migrations and seeding - seeding every program start

I recently changed an application from using the following for dev:

DropCreateDatabaseIfModelChanges<Context>


To using:

public class MyDbMigrationsConfiguration: DbMigrationsConfiguration<GrsEntities>
{
    public MyDbMigrationsConfiguration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = true;
    }
}


In my db context I have:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // Tell Code First to ignore PluralizingTableName convention
    // If you keep this convention then the generated tables will have pluralized names.
    modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

    //set the initializer to migration
    Database.SetInitializer(new MigrateDatabaseToLatestVersion<GrsEntities, MigrationConfig>());
}

I have overridden Seed(context) in DbMigrationsConfiguration using the AddOrUpdate extension where I was just using Add before with seeding on the drop db (DropCreateDatabaseIfModelChanges).

My confusion is that the Migration runs with each start of the application regardless of there being any changes to the DbContext. Each and every time I start the application (library run through a service) the initializer runs as does the Seed. My expected behaviour is a check whether a migration is necessary (behind the scenes check to see if model matches physical db) then update any new/removed tables/columns and only run seed if something has changed.

In my testing seed runs every time, which is workable but seemingly inefficient and was not what I expected. Unfortunately the MSDN documentation is quite limited.

Am I completely misusing MigrateDatabaseToLatestVersion? Is there any way to get the behaviour I expect (i.e. only seed if there is a model change) or should I just change my seed method to expect to be run every application launch?

like image 515
shox Avatar asked May 30 '12 19:05

shox


3 Answers

The fact that the Seed method ran only when the database changed was quite limiting for the database initializers that shipped in EF 4.1. It was limiting because sometimes you needed to update seed data without changing the database, but to make that happen you had to artificially make it seem like the database had changed.

With Migrations the use of Seed became a bit different because it could no longer be assumed that the database was starting empty--that's kind of the point of Migrations after all. So a Seed method in Migrations has to assume that the database exists and may already have data in it, but that data might need to be updated to take into account the changes made to the database for the Migrations. Hence the use of AddOrUpdate.

So now we have a situation where Seed must be written to take account of existing data, which means that there is really no need to perpetuate the limitations of the EF 4.1 Seed method such that you would have to make it seem like the database had changed just in order to get Seed to run. Hence Seed now runs every time the context is used for the first time in the app domain. This shouldn't change the way Seed is imlemented since it needs to handle the case where the data is already present anyway.

If it causes perf issues because you have a lot of Seed data then it is usually quiet easy to add checks into the Seed method that query the database to determine how much work needs to be done before doing it.

like image 101
Arthur Vickers Avatar answered Nov 18 '22 00:11

Arthur Vickers


I somewhat agree with Arthur Vickers response, however IMO Seed is for DbMigrations and I don't want the Seed method to be checking everything every time, e.g. If I have 4 migrations then I would need to test somehow which data must be seeded and that will be 4 more database hits at least. In case you still would like to have the behavior of running Seed method only when migrations are applied, like me, I came with my own implementation of the IDatabaseInitializer strategy

public class CheckAndMigrateDatabaseToLatestVersion<TContext, TMigrationsConfiguration>
    : IDatabaseInitializer<TContext>
    where TContext : DbContext
    where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
{
    public virtual void InitializeDatabase(TContext context)
    {
        var migratorBase = ((MigratorBase)new DbMigrator(Activator.CreateInstance<TMigrationsConfiguration>()));
        if (migratorBase.GetPendingMigrations().Any())
            migratorBase.Update();
    }
}
like image 18
Guillermo Ruffino Avatar answered Nov 18 '22 02:11

Guillermo Ruffino


Another option might be to load a custom db initializer class at runtime within the seed method. The Production app could then load a dummy initializer, while the dev app could load the real initializer. You could use Unity/MEF

    // Unity Dependency Injection Prop
    [Dependency]
    property IMyInitializer initializer;

    protected override Seed(YourContextClass context)
    {
       initializer.Seed(context);
    }

Something like that. You'd then switch initializers once you have the DB set-up in Production, to the dummy one, that would do nothing.

like image 2
Gary Gaughan-Smith Avatar answered Nov 18 '22 02:11

Gary Gaughan-Smith