6

Is there a simple way to manually run the Entity Framework migration Seed() method from the package-manager console, including after a Down() migration (Update-Database -TargetMigration foo)? The answer to "Run Code First Migration Seed Method without a migration" addresses how to manually run the Seed() method, which is to call Update-Database, but this doesn't work when the database is on an older migration and should not be updated. The same question was asked in "How to run Seed() method of Configuration class of migrations" but it was asked as part of a multi-part question, and this part remains unanswered (though the entire question is marked as answered now).

I'm asking because I have some cleanup code which needs to be run after a migration has been applied. Calling it from Seed() works fine for an Up() migration. I don't know how to easily call it for a Down() migration, though. I'd like a simple, one or two liner that would work from the package-manager. I know I could invoke the c# method after calling [Reflection.Assembly]::LoadFile() for all the necessary DLLs, but there are enough dependencies that this would be cumbersome and error prone.

I know that Seed() isn't an ideal place for Down() migration cleanup code (to add some context based on DrewJordan's answer), but using Down() itself is unfortunately non-viable for several reasons. From a practical standpoint, it is non-viable because the cleanup has to convert several gigabytes worth of data, and this caused SQL Server Express to crash when I tried a simple copy in Down(), probably because the resulting transaction size was enormous. Second, from even a hypothetical standpoint, I don't believe there is a way to convert the data to the extent needed in Down() because current SQL rows cannot be read in c# as part of the transaction, and the conversion of the data needs to take place in c#. For details on that limitation, see my other question: "Transform data using c# during Entity Framework migration". Some compromise of the design was needed to get the cleanup to work, and I was using Seed() as that compromise. The cleanup code doesn't need to be run from Seed() per se, but I don't know what else can be invoked from the package-manager without changing the current migration.

There is also another scenario where it would be useful to invoke Seed(), or other cleanup code, by itself without changing the current migration. Consider the scenario where there was a bug in the Seed()/cleanup method. You fix the bug and want to re-run it without changing the current migration. Putting the cleanup logic in Down() would not solve the problem because Down() won't be invoked when the database is already on that migration.

Community
  • 1
  • 1
twm
  • 1,448
  • 11
  • 19
  • 1
    I'm afraid this cannot be achieved with Seed method. You might want to fork EntityFramework and add migration interception there, but most likely you just have to think about another way of implementing this. What's the reason behind this huge code cleanup for every migration? – Red Mar 08 '16 at 05:08
  • @raderick Whoa - I didn't realize the source was available for the Entity Framework. Thanks! I'm going to dig around it to see if I can find another workaround. ... The cleanup is for one specific migration, there just doesn't seem to be a way to do the conversion in Up()/Down() because the transformation is done in c#. The data set is what's huge. – twm Mar 08 '16 at 13:17
  • Can your goal be achieved with just adding this migration code as a unit-test and mark it with [Ignore] attribute, and run it manually? – Red Mar 08 '16 at 18:55
  • @raderick a unit test sounds like it could work too, thanks. I did figure out how to do it from the package-manager console incidentally - I posted my approach below: http://stackoverflow.com/a/35873514/1552934 – twm Mar 08 '16 at 19:13
  • I saw your post, but sadly this type of code might come to weird side effects - low chance, but still, I don't personally feel safe when face such code and have no contact to the one who wrote it. – Red Mar 08 '16 at 19:20
  • I did attempt to limit the side effects, first and foremost by only running the code when explicitly enabled. It's off by default. There might be side effects if a context whose model is out of date with the current database is used, but I think that's probably true regardless of how the cleanup gets triggered, including the unit test approach. In my particular use case, the cleanup code only actually uses the context to get at the db.Database connection and make direct SQL queries, so a model that doesn't match the database won't matter. – twm Mar 08 '16 at 21:18

2 Answers2

1

I realize you asked about running it from the PM console: AFAIK it's not possible. This use case isn't what Seed is for: I would recommend if you have changes to make in a Down method, either write SQL scripts to run, or create a context in Down and do whatever there.

It's a bad idea because Seed counts on being up to date with the latest migration. It will allow you, for example, to write statements that can't possibly succeed on a prior version of your context, such as when a column is added to the model but not yet applied to the database. I haven't tried this but it'll throw an exception at the least, and possibly fail to write any changes.

If you really, really think this is a good idea, you can add a method into your configuration:

public void CallSeed()
{
    using (var c = new Context()) // create your context
    {
        Seed(c);
    }
}

And call it from Down:

var c = new Configuration();
c.CallSeed();
DrewJordan
  • 5,266
  • 1
  • 25
  • 39
  • Thanks for the response. I would have liked to have used Down() for the cleanup, but it is unfortunately not viable for conversions of data where the conversion must occur in c# and also not viable for large conversions. I updated my question to expand on this. – twm Mar 07 '16 at 15:12
1

I found the basis for something that looks like it will work. It's a hack, but I'll post it here because I know others have wanted something similar, judging from the other stack overflow questions I linked to.

The idea is to piggyback on another migration command that can be run from the package-manager and which wouldn't alter the database itself - I'll use Get-Migrations . This will allow the package-manager command to do the heavy lifting of locating the right configurations, loading the right assemblies, etc. I'll piggyback on that command by calling my cleanup code from the constructor of my DbMigrationsConfiguration subclass. I want to only run the cleanup code intentionally, not every time the Configuration is constructed, so I will also check for a sentinel environment variable before actually running the cleanup code. Later, I plan to move the cleanup code out of Seed() in order to separate it from the actual seeding, but since others asked about manually running Seed() in the past, I'll use that as the example here.

I changed my DbMigrationsConfiguration subclass as follows:

internal sealed class Configuration : DbMigrationsConfiguration<TimsDB>
{

    public Configuration()
    {
        if ("true".Equals(Environment.GetEnvironmentVariable("RUN_SEED")))
            using (TimsDB db = new TimsDB())
            {
                Database.SetInitializer<TimsDB>(null);
                Seed(db);
            }
        }
    }

    ...

}

This can then be used to invoke the Seed() method manually from the package-manager:

PM> $Env:RUN_SEED = "true"
PM> Get-Migrations
PM> $Env:RUN_SEED = "false"
twm
  • 1,448
  • 11
  • 19