2

I have a weird problem with Entity Framework code first migrations. I've been using EF and code first migrations on a project for months now and things are working fine. I recently created a new migration and when running Update-Database a restored backup of my database I get this error:

The model backing the context has changed since the database was created. Consider using Code First Migrations to update the database

The migration does something like the following:

public override void Up()
{
    using (SomeDbContext ctx = new SomeDbContext())
    {
        //loop through table and update rows
        foreach (SomeTable table in ctx.SomeTables)
            table.SomeField = DoSomeCalculation(table.SomeField);

        ctx.SaveChanges();
    }
}

I'm not using the Sql() function because DoSomeCalculation must be done in C# code.

Usually when I get something like this is means that I have updated my model somehow and forgot to create a migration. However that's not the case this time. The weird thing is that the error isn't even occurring on a migration that I created a few days ago and had been working fine.

I looked a quite a few articles about this and they all seems to say call

Database.SetInitializer<MyContext>(null);

Doing that does seem to work, but my understanding (based on this article) is that doing that will remove EF's ability to determine when the database and model are out of sync. I don't want to do that. I just want to know why it thinks they are out of sync all of a sudden.

I also tried running Add-Migration just to see if what it thought changed about the model but it won't let me do that stating that I have pending migrations to run. Nice catch 22, Microsoft.

Any guesses as to what's going on here?

I'm wondering if maybe the fact that migration listed above is using EntityFramework is the problem. Seems like maybe since it's not the latest migration anymore, when EF gets to it tries to create a SomeDbContext object it checks the database (which is not fully up to date yet since we're in the middle of running migrations) against my current code model and then throws the "context has changed" error.

Community
  • 1
  • 1
d512
  • 32,267
  • 28
  • 81
  • 107
  • What version of EF, and how old is your database backup (i.e. how many migrations are you expecting it to have applied?) – Richard Feb 26 '15 at 16:37
  • All migrations are EF 6.1.1-30610. The backup should have about 70 in it and my latest code should apply another 3. – d512 Feb 26 '15 at 16:41
  • What's changed in your code in the mean time? Have you referenced additional libraries? Do your models live in the same assembly as your program? – Richard Feb 26 '15 at 16:43
  • I did add some new fields to the model and generate a new migration. That's about it. No other major changes. – d512 Feb 26 '15 at 16:45
  • Hm...I just thought of something. I'm using EF inside the migration that is blowing up. It fails when it tries to create the DbContext object. Keep in mind that migration is not the latest one anymore. Is it possible that when EF gets to that migration it's checking the latest version of the code model against the current version of the database (which isn't fully up to date yet because we're in the middle or running the migrations) and throwing this error? That would explain this migration worked fine when it was the latest one and no longer works now. – d512 Feb 26 '15 at 16:50
  • Sounds possible. Perhaps post a cut down version showing what happens in that migration. – Richard Feb 26 '15 at 16:52
  • I updated the question with the code. – d512 Feb 26 '15 at 17:05

2 Answers2

1

It's possibly related to your using EF within the migration. I'm not sure how you're actually managing this, unless you've set a null database initialiser.

If you need to update data within a migration, use the Sql function, e.g.

Sql("UPDATE SomeTable SET SomeField = 'Blah'");

You should note that the Up() method is not actually running at the time of doing the migration, it's simply used to set up the migration which is then run later. So although you may think you've done something in the migration above the bit where you're using EF, in reality that won't have actually run yet.

If you cannot refactor your calculation code so it can be written in SQL, then you would need to use some mechanism other than migrations to run this change. One possibility would be to use the Seed method in your configuration, but you would need to be aware that this does not keep track of whether the change has been run or not. For example...

internal sealed class Configuration : DbMigrationsConfiguration<MyContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }

    protected override void Seed(MyContext context)
    {
        // Code here runs any time ANY migration is performed...
    }
}
Richard
  • 29,854
  • 11
  • 77
  • 120
  • Is there any problem creating a regular ADO.NET SqlConnection to the database inside a migration? I could do that to read all the data from the table, calculate all the new versions of SomeField, then use Sql() to update each row with the new values. – d512 Feb 26 '15 at 17:12
  • The whole point of the migration is to modify the data inside the SomeField column of each row. I can't use the Sql function alone for that because the stuff inside DoSomeCalculation() requires a .NET library. It can't be done with T-SQL. – d512 Feb 26 '15 at 17:16
  • Updated answer with a suggestion about using Seed – Richard Feb 26 '15 at 18:32
  • Can you elaborate a little bit on the statement "You should note that the Up() method is not actually running at the time of doing the migration, it's simply used to set up the migration which is then run later. So although you may think you've done something in the migration above the bit where you're using EF, in reality that won't have actually run yet." Is there some documentation or something I can look at to read up more on this? – d512 Feb 26 '15 at 22:15
  • Check out my proposed solution. It uses ADO.NET and seems to work, but I'm concerned about your statement that I'll have to use something other than code first migrations. – d512 Feb 26 '15 at 22:25
  • Not sure where it's documented, I found it by looking through the source for [DbMigration](https://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Migrations/DbMigration.cs). You'll note that every operation simply calls AddOperation, which just creates a list of things to be added later. Thus if you create a migration which creates a table and then uses ADO to populate it in the same migration, the table will not exist when your ADO code runs. If your migration is JUST for performing your calculations, then your ADO method should work. – Richard Feb 26 '15 at 22:36
  • Interesting. I didn't even know EF was an open source project. Microsoft doing open source--how times have changed... But I see what you mean about AddOperation. I guess it queues up a bunch of changes and runs them in sequence. It's disappointing that they force you to use the Sql() method to work properly with this architecture when it's so incredibly limited. Anyway, all the ADO.NET code is doing is querying some data so I think it should be good to go. Thank you for all the help. – d512 Feb 26 '15 at 22:58
0

I tried replacing the EntityFramework code with regular ADO.NET code and it seems to work. Here is what it looks like:

public override void Up()
{
    Dictionary<long, string> idToNewVal = new Dictionary<long, string>();

    using (SqlConnection conn = new SqlConnection("..."))
    {
        conn.Open();

        using (SqlCommand cmd = new SqlCommand("SELECT SomeID, SomeField FROM SomeTable", conn))
        {
            SqlDataReader reader = cmd.ExecuteReader();

            //loop through all fields, calculating the new value and storing it with the row ID
            while (reader.Read())
            {
                long id = Convert.ToInt64(reader["SomeID"]);
                string initialValue = Convert.ToString(reader["SomeField"]);
                idToNewVal[id] = DoSomeCalculation(initialValue);
            }
        }
    }

    //update each row with the new value
    foreach (long id in idToNewVal.Keys)
    {
        string newVal = idToNewVal[id];
        Sql(string.Format("UPDATE SomeTable SET SomeField = '{0}' WHERE SomeID = {1}", newVal, id));
    }
}
d512
  • 32,267
  • 28
  • 81
  • 107