I'm working on an ASP.NET Web API 2 project, using .NET Framework 4.8 and Entity Framework 6, and I'm facing an issue with custom migrations.
I need to deal with two situations, one where there is a new deployment, in which the database needs to be created, and another one where I need to update an existing database from a previous deployment.
I've created a custom database initializer to apply specific migrations based on custom rules, and it works well when the database doesn't exist, but I'm encountering problems when trying to update an existing database.
The project was created a few years ago without migrations enabled, I enabled the migrations in this version, the Enable-Migrations script detected an existing model and generated an initial create script: 202406261422299_InitialCreate.cs.
Then I created two more migrations, as described below, in a simplified manner:
202406261422299_InitialCreate.cs- adds a couple of tables, including aUsertable202406111558374_AddCategoryTable.cs- adds aCategorytable202406121657845_AddDetailsColumnToCategoryTable.cs- adds aDetailscolumn to theCategorytable
The scenarios are:
New deployment when the database doesn't exist
This works as expected, all the migrations are applied.Existing database state
As mentioned, the project was created a few years ago without migrations enabled, but the__MigrationHistorytable exists and includes a single row. The reason for this is that the developer working at the time decided to execute a single empty (no code inUp()orDown()) migration manually. The database is created when the context is constructed, with all the tables for the existing DbSets, including the User table. I believe this happens due to the defaultCreateDatabaseIfNotExistsinitializer strategy. Then the pseudo-migration is run and this creates the__MigrationHistorytable and a single row is added for this, with theMigrationIdset to the<CurrentTime>_InitialCreate, e.g.'202307170848501_InitialCreate'.
What I need for this scenario is to skip the initial migration.
I wrote a CustomDbInitializer based on MyContext class:
public class CustomDbInitializer : IDatabaseInitializer<MyContext>
{
public void InitializeDatabase(MyContext context)
{
if (!context.Database.Exists())
{
context.Database.Create();
Seed(context);
return;
}
var migrator = new DbMigrator(new Configuration());
var appliedMigrations = migrator.GetDatabaseMigrations();
var pendingMigrations = migrator.GetPendingMigrations();
Log.Verbose("[CustomDbInitializer] Applied migrations: {AppliedMigrations}", string.Join(", ", appliedMigrations));
Log.Verbose("[CustomDbInitializer] Pending migrations: {PendingMigrations}", string.Join(", ", pendingMigrations));
foreach (var migration in pendingMigrations)
{
if (ShouldApplyMigration(migration, appliedMigrations))
{
Log.Verbose("[CustomDbInitializer] Applying migration {Migration}", migration);
migrator.Update(migration);
Log.Verbose("[CustomDbInitializer] Migration {Migration} applied.", migration);
}
}
}
private bool ShouldApplyMigration(string migrationId, IEnumerable<string> appliedMigrations)
{
if (!appliedMigrations.Contains(migrationId))
{
return !migrationId.Contains("InitialCreate");
}
return false;
}
protected void Seed(MyContext context)
{
}
}
I'm encountering the following issues:
- Missing pending migration
When running the custom initializer, the pendingMigrations list does not include 202406261422299_InitialCreate, even though the __MigrationHistory table has a different InitialCreate migration with id 202307170848501_InitialCreate.
Here are the corresponding log messages:
[08:39:27:728 VRB] [CustomDbInitializer] Applied migrations: 202307170848501_InitialCreate
[08:39:27:728 VRB] [CustomDbInitializer] Pending migrations: 202406111558374_AddCategoryTable, 202406121657845_AddDetailsColumnToCategoryTable
Could it be that DbMigrator checks the Model column and decides somehow that these two InitialCreate migrations are the same?
- Error on applying
AddDetailsColumnToCategoryTablemigration
The log continues with:
[08:39:27:729 VRB] [CustomDbInitializer] Applying migration 202406111558374_AddCategoryTable ...
[08:39:28:253 VRB] [CustomDbInitializer] Migration 202406111558374_AddCategoryTable applied.
[08:39:28:253 VRB] [CustomDbInitializer] Applying migration 202406121657845_AddDetailsColumnToCategoryTable ...
System.Data.SqlClient.SqlException (0x80131904): There is already an object named 'User' in the database.
The exception is thrown from migrator.Update(migration). This is puzzling because the AddDetailsColumnToCategoryTable migration does not reference the User table.
To conclude, my questions are:
- Why do I get an error about the
Usertable when applying theAddDetailsColumnToCategoryTablemigration, even though it doesn't reference theUsertable? - Why is the
202406261422299_InitialCreatemigration not appearing in thependingMigrationslist, despite the__MigrationHistorytable having a differentInitialCreatemigration id? - Is there a better way to deal with my situation?
Please note that I'm aware of the answers here and here, used in the situation with an existing database, but I need to handle both situations, existing database and no database.
I would greatly appreciate any insights or solutions to these issues.
Thank you
Usertable.var pendingMigrations = migrator.GetPendingMigrations();