asp.netvb.netentity-frameworkms-accessjet-ef-provider

HowTo update every row with unique value on migration using entity framework code first implementation


I am looking for an appropriate way to expand an existing database, based on entity framework 6 code first implementation, and adding unique values to the new field for every existing row in the table.

So far I have created a migration which adds the new field.

The Up method looks like this:

Public Overrides Sub Up()
    AddColumn("dbo.Customers", "UniqueCode", Function(c) c.String(unicode:=False))
End Sub

I am stuck at the point where the new field should be filled with a unique (calculated) value. To keep it simple, let's say every existing row in the database should be assigned a GUID upon the migration.

Using an SQL statement like this would update all rows with the same GUID. But I need it to be unique for every row.

Sql("Update dbo.Customers SET UniqueCode = '" & Guid.NewGuid().ToString)

Using a foreach in the Up method seems kind of wrong... What is best practice in this case?

In addition: The database I am using is access, so I can't use newid() or random(). The GUID is meant to be a dummy for a programmatically calculated value. It will be a hashed value of some other attributes of the customer. So it must be calculated and updated with migration.


Solution

  • I have found the following approach to be the best fit for my situation. I recommend splitting the database and data changes in 2 migrations, which allows you to use the framework for all changes and be fully up and down compatible.

    Here the details to my approach:

    Following Bradley Uffner's advice I loop through the data and update it row for row. But, doing this within one and the same migration, throws an error:

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

    Using a DbContext in the middle of it's migration leads to an inconsistent state. The model is already in its after migration state, but the database still has the before migration state in the tables. Model and database do not match, which leads to the above error. To make this work, I would have to disable model checking.

    Reference: Change data in migration Up method - Entity Framework

    In the above thread I found the suggestion to separate database changes from data changes. That's exactly what I did.

    I created one migration to update the database

        Public Overrides Sub Up()
            AddColumn("dbo.Customers", "WebCode", Function(c) c.String(unicode := false))
        End Sub
    
        Public Overrides Sub Down()
            DropColumn("dbo.Customers", "WebCode")
        End Sub
    

    Run the Update-Database command. And then create a second migration to make the data changes. The Up and Down methods will be empty upon creation. Here is the code I use to update the data row by row.

        Public Overrides Sub Up()
            Dim DbContext As New Data.DbContext
    
            For Each customer In DbContext.Customers.Where(Function(x) String.IsNullOrEmpty(x.WebCode))
                customer.WebCode = GetWebCode(customer)
            Next
    
            DbContext.SaveChanges()
        End Sub
    
        Public Overrides Sub Down()
            Dim DbContext As New Data.DbContext
    
            For Each customer In DbContext.Customers
                customer.WebCode = Nothing
            Next
    
            MyDbContext.SaveChanges()
        End Sub
    

    Some might argue using a simple SQL in the Down method like

    SQL("Update dbo.Customers SET WebCode = NULL")
    

    is more efficient. I have tried that, but encountered an error at JetEntityFrameworkProvider.JetMigrationSqlGenerator.GenerateSqlStatmentConcrete which I was not able to locate and fix. It made Visual Studio crash.