0

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 a User table
  • 202406111558374_AddCategoryTable.cs - adds a Category table
  • 202406121657845_AddDetailsColumnToCategoryTable.cs - adds a Details column to the Category table

The scenarios are:

  1. New deployment when the database doesn't exist
    This works as expected, all the migrations are applied.

  2. Existing database state
    As mentioned, the project was created a few years ago without migrations enabled, but the __MigrationHistory table 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 in Up() or Down()) 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 default CreateDatabaseIfNotExists initializer strategy. Then the pseudo-migration is run and this creates the __MigrationHistory table and a single row is added for this, with the MigrationId set 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 AddDetailsColumnToCategoryTable migration

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:

  1. Why do I get an error about the User table when applying the AddDetailsColumnToCategoryTable migration, even though it doesn't reference the User table?
  2. Why is the 202406261422299_InitialCreate migration not appearing in the pendingMigrations list, despite the __MigrationHistory table having a different InitialCreate migration id?
  3. 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

4
  • Have you checked the generated migrations code? can you share them? seems in one of them trying to create User table. Commented Jun 27, 2024 at 11:02
  • The AddDetailsColumnToCategoryTable migration only adds a Details column to the Category table. There is no mention of the User table in there. In the 202406261422299_InitialCreate migration, the User table is indeed created, but that migration doesn't run in my case. Commented Jun 27, 2024 at 12:34
  • How you get the Pending migrations? Commented Jun 27, 2024 at 14:53
  • It's in the code from the description: var pendingMigrations = migrator.GetPendingMigrations(); Commented Jun 28, 2024 at 6:09

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.