Skip to content

Calling Migrate on a database containing tables but no history throws #35181

@ajcvickers

Description

@ajcvickers

This repro was extracted from this comment by @PureIso.

This is a case where there is a regression in behavior between EF8 and EF9, but there are likely to be other root causes for the exception. I was not able to find a case where Up/Down are empty using this repro, although a better understanding of the root cause may reveal this.

Consider code that creates tables in the DbContext constructor:

    public ContextOfDoom()
    {
        if (Database.GetService<IDatabaseCreator>() is not RelationalDatabaseCreator dbCreate) return;
        // Create Database 
        if (!dbCreate.CanConnect())
        {
            dbCreate.Create();
        }
        
        // Create Tables
        if (!dbCreate.HasTables())
        {
            dbCreate.CreateTables();
        }
    }

This creates any tables that don't exist, but does not create the migrations history table:

>>>>>>>>>>>>>>>>>>>> Starting tables

warn: 11/22/2024 10:59:37.598 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure) 
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
info: 11/22/2024 10:59:37.875 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (17ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 11/22/2024 10:59:37.893 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (10ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      
      IF EXISTS
          (SELECT *
           FROM [sys].[objects] o
           WHERE [o].[type] = 'U'
           AND [o].[is_ms_shipped] = 0
           AND NOT EXISTS (SELECT *
               FROM [sys].[extended_properties] AS [ep]
               WHERE [ep].[major_id] = [o].[object_id]
                   AND [ep].[minor_id] = 0
                   AND [ep].[class] = 1
                   AND [ep].[name] = N'microsoft_database_tools_support'
          )
      )
      SELECT 1 ELSE SELECT 0

>>>>>>>>>>>>>>>>>>>> Done tables

Then, when the application starts, it calls Database.Migrate:

using (var context = new ContextOfDoom())
{
    context.Database.Migrate();
}

In EF8, this creates the migration history table:

>>>>>>>>>>>>>>>>>>>> Starting migrate

info: 11/22/2024 11:01:49.741 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 11/22/2024 11:01:49.745 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT OBJECT_ID(N'[__EFMigrationsHistory]');
info: 11/22/2024 11:01:49.745 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 11/22/2024 11:01:49.819 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [__EFMigrationsHistory] (
          [MigrationId] nvarchar(150) NOT NULL,
          [ProductVersion] nvarchar(32) NOT NULL,
          CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
      );
info: 11/22/2024 11:01:49.820 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 11/22/2024 11:01:49.821 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT OBJECT_ID(N'[__EFMigrationsHistory]');
info: 11/22/2024 11:01:49.824 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [MigrationId], [ProductVersion]
      FROM [__EFMigrationsHistory]
      ORDER BY [MigrationId];
info: 11/22/2024 11:01:49.834 RelationalEventId.MigrationsNotApplied[20405] (Microsoft.EntityFrameworkCore.Migrations) 
      No migrations were applied. The database is already up to date.

>>>>>>>>>>>>>>>>>>>> Done Migrate

But in EF9, it throws:

>>>>>>>>>>>>>>>>>>>> Starting migrate

Unhandled exception. System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Migrations.PendingModelChangesWarning': The model for context 'ContextOfDoom' has pending changes. Add a new migration before updating the database. This exception can be suppressed or logged by passing event ID 'RelationalEventId.PendingModelChangesWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
   at Microsoft.EntityFrameworkCore.Diagnostics.EventDefinition`1.Log[TLoggerCategory](IDiagnosticsLogger`1 logger, TParam arg)
   at Microsoft.EntityFrameworkCore.Diagnostics.RelationalLoggerExtensions.PendingModelChangesWarning(IDiagnosticsLogger`1 diagnostics, Type contextType)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
   at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
   at Program.<Main>$(String[] args) in D:\code\DoomSlug\DoomSlug\Program.cs:line 35
   at Program.<Main>(String[] args)

Process finished with exit code -532,462,766.

Full code:

using (var context = new ContextOfDoom())
{
    Console.WriteLine();
    Console.WriteLine(">>>>>>>>>>>>>>>>>>>> Starting migrate");
    Console.WriteLine();
    context.Database.Migrate();
    Console.WriteLine();
    Console.WriteLine(">>>>>>>>>>>>>>>>>>>> Done Migrate");
    Console.WriteLine();
    
    //await context.Database.EnsureDeletedAsync();
}

public class ContextOfDoom : DbContext
{
    public ContextOfDoom()
    {
        Console.WriteLine();
        Console.WriteLine(">>>>>>>>>>>>>>>>>>>> Starting tables");
        Console.WriteLine();

        if (Database.GetService<IDatabaseCreator>() is not RelationalDatabaseCreator dbCreate) return;
        // Create Database 
        if (!dbCreate.CanConnect())
        {
            dbCreate.Create();
        }
        
        // Create Tables
        if (!dbCreate.HasTables())
        {
            dbCreate.CreateTables();
        }

        Console.WriteLine();
        Console.WriteLine(">>>>>>>>>>>>>>>>>>>> Done tables");
        Console.WriteLine();
    }

    public DbSet<Product> Products => Set<Product>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .EnableSensitiveDataLogging()
            .LogTo(Console.WriteLine, LogLevel.Information)
            .UseSqlServer(
                "Server=localhost;Database=DoomSlug;Trusted_Connection=True;TrustServerCertificate=True");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>();
    }
}

public class Product
{
    public int Id { get; set; }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions