Skip to content

Regression from EF Core 8 to 9: MigrationBuilder.DropTable Causes Issues with Subsequent Table Recreation #35162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
meghanmae opened this issue Nov 20, 2024 · 15 comments · Fixed by #35764

Comments

@meghanmae
Copy link

meghanmae commented Nov 20, 2024

Problem

After upgrading from EF Core 8 to EF Core 9, we encountered a regression where using migrationBuilder.DropTable followed by recreating the same table in a single migration causes issues. The issue seems to be resolved when switching to a raw SQL command (migrationBuilder.Sql("DROP TABLE ...");) to drop the table instead of using migrationBuilder.DropTable.

        protected override void Up(MigrationBuilder migrationBuilder)
        {
            // Assuming table Employees was previously created...

            // DropTable: causes the migration to fail
            migrationBuilder.DropTable(
                name: "Employees");

            // Uncomment this (and comment out DropTable) to run the migration successfully 
            //migrationBuilder.Sql("""
            //    Drop Table Employees;
            //    """);

            // Recreate table
            migrationBuilder.CreateTable(
                name: "Employees",
                columns: table => new
                {
                    EmployeeId = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Name = table.Column<string>(type: "nvarchar(max)", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Employees", x => x.EmployeeId);
                });
        }

Exception & stack tack traces

System.Collections.Generic.KeyNotFoundException: 'The given key '(Employees, )' was not present in the dictionary.'
 	[Exception] System.Private.CoreLib.dll!System.ThrowHelper.ThrowKeyNotFoundException<T>(T key)	Unknown
 	[Exception] System.Private.CoreLib.dll!System.Collections.Generic.Dictionary<TKey, TValue>.this[TKey].get(TKey key)	Unknown
 	[Exception] Microsoft.EntityFrameworkCore.SqlServer.dll!Microsoft.EntityFrameworkCore.Migrations.SqlServerMigrationsSqlGenerator.RewriteOperations(System.Collections.Generic.IReadOnlyList<Microsoft.EntityFrameworkCore.Migrations.Operations.MigrationOperation> migrationOperations, Microsoft.EntityFrameworkCore.Metadata.IModel model, Microsoft.EntityFrameworkCore.Migrations.MigrationsSqlGenerationOptions options)	Unknown
 	[Exception] Microsoft.EntityFrameworkCore.SqlServer.dll!Microsoft.EntityFrameworkCore.Migrations.SqlServerMigrationsSqlGenerator.Generate(System.Collections.Generic.IReadOnlyList<Microsoft.EntityFrameworkCore.Migrations.Operations.MigrationOperation> operations, Microsoft.EntityFrameworkCore.Metadata.IModel model, Microsoft.EntityFrameworkCore.Migrations.MigrationsSqlGenerationOptions options)	Unknown
 	[Exception] Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.GenerateUpSql(Microsoft.EntityFrameworkCore.Migrations.Migration migration, Microsoft.EntityFrameworkCore.Migrations.MigrationsSqlGenerationOptions options)	Unknown
 	[Exception] Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.GetMigrationCommandLists.AnonymousMethod__2()	Unknown
 	[Exception] Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.MigrateImplementation(Microsoft.EntityFrameworkCore.DbContext context, string targetMigration, Microsoft.EntityFrameworkCore.Migrations.MigrationExecutionState state, bool useTransaction)	Unknown
 	[Exception] Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate.AnonymousMethod__20_1(Microsoft.EntityFrameworkCore.DbContext c, (Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator Migrator, string TargetMigration, Microsoft.EntityFrameworkCore.Migrations.MigrationExecutionState State, bool UseTransaction) s)	Unknown
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.Execute.AnonymousMethod__0(Microsoft.EntityFrameworkCore.DbContext context, TState state)	Unknown
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementation<TState, TResult>(System.Func<Microsoft.EntityFrameworkCore.DbContext, TState, Microsoft.EntityFrameworkCore.Storage.ExecutionResult<TResult>> operation, System.Func<Microsoft.EntityFrameworkCore.DbContext, TState, Microsoft.EntityFrameworkCore.Storage.ExecutionResult<TResult>> verifySucceeded, TState state)	Unknown
 	[Exception] Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.Execute<TState, TResult>(TState state, System.Func<Microsoft.EntityFrameworkCore.DbContext, TState, TResult> operation, System.Func<Microsoft.EntityFrameworkCore.DbContext, TState, Microsoft.EntityFrameworkCore.Storage.ExecutionResult<TResult>> verifySucceeded)	Unknown
 	[Exception] Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(string targetMigration)	Unknown
 	[Exception] Microsoft.EntityFrameworkCore.Relational.dll!Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade databaseFacade)	Unknown
>	[Exception] MyApp.Web.dll!Program.<Main>$(string[] args) Line 195	C#

Versions

EF Core version: 9.0.0
Database provider: Microsoft SQL Server (LocalDB)
Target framework: .NET 9.0
Operating system: Windows 11
IDE: Visual Studio 2022 17.12.0

@devedse
Copy link

devedse commented Nov 26, 2024

We are running into the same issue on our customer code base. The workaround also works for us.

@roji
Copy link
Member

roji commented Nov 27, 2024

Poaching (have a bit of extra capacity to help with 9.0 regressions for the 9.0.1 cutoff). Confirmed regression from 8.0 to 9.0.

Repro
await using var context = new BlogContext();
await context.Database.EnsureDeletedAsync();
await context.Database.MigrateAsync();

public class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer("Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0;Encrypt=false")
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();
}

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Generate an initial migration for the above, then add another empty migration, and paste the following into it:

            migrationBuilder.DropTable(
                name: "Blogs");

            migrationBuilder.CreateTable(
                name: "Blogs",
                columns: table => new
                {
                    Id = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Name = table.Column<string>(type: "nvarchar(max)", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Blogs", x => x.Id);
                });

@roji
Copy link
Member

roji commented Nov 27, 2024

@meghanmae and @AnthonyDewhirst, can you please confirm how you ended up with a single migration that has a DropTable and then a CreateTable on the same migration? Is this the result of manual editing of the migration, or did EF generate this itself? If it's the latter, can you provide more info on that?

@AnthonyDewhirst
Copy link

Hi @roji apologise once more...

By the time I got this all fixed which included rolling back a migration in code to see what 9.0.0 generated, I realise that there should never have been a drop table. This was a case of 2 of my devs doing competing work and checking the code in and fixing it, by instead of deleting the migrations and crafting one again, by hand manipulating the files.

So, yep, our bad, not something should have ever made it passed a PR but had worked previously. I used the raw sql work around. But it's on my todo list to go generate the migrations properly. It's live so I need to be careful, but I think it's straight forward.

So from my perspective, this is user error and not an issue we have with ef core.

I did mean to write this in early and was also on my todo list.

Thanks for the great work, much appreciated!

@molinch
Copy link

molinch commented Dec 24, 2024

@roji Is there a prerelease package that would have the fix applied for it?

@NKnusperer
Copy link
Contributor

Can we expect a fix for 9.0 ?

@roji
Copy link
Member

roji commented Jan 31, 2025

@maumar to answer regarding backporting (and/or a workaround) - the current fix looks a bit large and risky for that.

@TimPurdum
Copy link

We ran into this as well. A common scenario where we have to do Drop/Add manually in a migration is that EF Core will occasionally build invalid Updates, like when a column changes type, that would fail on run. I don't have an exact repro, but hopefully that makes sense.

@codehunter13
Copy link

same problem over here

@roji
Copy link
Member

roji commented Feb 19, 2025

@maumar ping

@maumar
Copy link
Contributor

maumar commented Mar 11, 2025

We could port the fix that makes the temporal information dictionary access more robust - that would solve the problems for non-temporal scenarios that used to work but got hit by the temporal table re-design. However the proper fix for the temporal table issues is unrealistic to be ported to 9 IMO. It's a big re-design of how we handle it, likely including some API changes (to flow the starting model)

@roji
Copy link
Member

roji commented Mar 11, 2025

Yeah, IMHO it seems to be a regression that enough people are hitting to warrant a targeted fix (but can discuss in design).

maumar added a commit that referenced this issue Mar 11, 2025
…ilder.DropTable Causes Issues with Subsequent Table Recreation

Problem was that in 9.0 we changed how we process temporal tables, tracking the temporal table information as we process migrations. However, we didn't take into account the cases where users would manually modify their migrations, or squash multiple migrations together, resulting in corrupted dictionary access for cases where a table was deleted and created in the same migration.
This fix just makes the temporal information map handling more robust, so those errors don't happen anymore. There is a broader issue with temporal tables but those will be addressed in a different PR.

Fixes #35162
maumar added a commit that referenced this issue Mar 11, 2025
…ilder.DropTable Causes Issues with Subsequent Table Recreation

Problem was that in 9.0 we changed how we process temporal tables, tracking the temporal table information as we process migrations. However, we didn't take into account the cases where users would manually modify their migrations, or squash multiple migrations together, resulting in corrupted dictionary access for cases where a table was deleted and created in the same migration.
This fix just makes the temporal information map handling more robust, so those errors don't happen anymore. There is a broader issue with temporal tables but those will be addressed in a different PR.

Fixes #35162
maumar added a commit that referenced this issue Mar 11, 2025
…ilder.DropTable Causes Issues with Subsequent Table Recreation

Problem was that in 9.0 we changed how we process temporal tables, tracking the temporal table information as we process migrations. However, we didn't take into account the cases where users would manually modify their migrations, or squash multiple migrations together, resulting in corrupted dictionary access for cases where a table was deleted and created in the same migration.
This fix just makes the temporal information map handling more robust, so those errors don't happen anymore. There is a broader issue with temporal tables but those will be addressed in a different PR.

Fixes #35162
maumar added a commit that referenced this issue Mar 12, 2025
…ilder.DropTable Causes Issues with Subsequent Table Recreation

Problem was that in 9.0 we changed how we process temporal tables, tracking the temporal table information as we process migrations. However, we didn't take into account the cases where users would manually modify their migrations, or squash multiple migrations together, resulting in corrupted dictionary access for cases where a table was deleted and created in the same migration.
This fix just makes the temporal information map handling more robust, so those errors don't happen anymore. There is a broader issue with temporal tables but those will be addressed in a different PR.

Fixes #35162
maumar added a commit that referenced this issue Mar 12, 2025
…ilder.DropTable Causes Issues with Subsequent Table Recreation

Problem was that in 9.0 we changed how we process temporal tables, tracking the temporal table information as we process migrations. However, we didn't take into account the cases where users would manually modify their migrations, or squash multiple migrations together, resulting in corrupted dictionary access for cases where a table was deleted and created in the same migration.
This fix just makes the temporal information map handling more robust, so those errors don't happen anymore. There is a broader issue with temporal tables but those will be addressed in a different PR.

Fixes #35162
maumar added a commit that referenced this issue Mar 12, 2025
…Table Causes Issues with Subsequent Table Recreation

Port of #35764

Fixes #35162

Description
Problem was that in 9.0 we changed how we process temporal tables, tracking the temporal table information as we process migrations. However, we didn't take into account the cases where users would manually modify their migrations, or squash multiple migrations together, resulting in corrupted dictionary access for cases where a table was deleted and created in the same migration. This fix just makes the temporal information map handling more robust, so those errors don't happen anymore. There is a broader issue with temporal tables but those will be addressed in a different PR.

Customer impact
Some manually modified/squashed migrations no longer work when they are being applied, throwing an unhelpful exception (The given key '...' was not present in the dictionary.'). No good workaround apart from breaking the migrations apart.

How found
Reported by multiple customers on 9.

Regression
Yes.

Testing
Added infra to test these types of scenarios (containing intermediate migration state), added multiple tests both for regular and temporal tables.

Risk
Low - Very targeted fix, accessing the temporal information dictionary in a more robust way. No quirk, since migrations are design time, so hard to set switches for them.
maumar added a commit that referenced this issue Mar 12, 2025
…ilder.DropTable Causes Issues with Subsequent Table Recreation (#35764)

Problem was that in 9.0 we changed how we process temporal tables, tracking the temporal table information as we process migrations. However, we didn't take into account the cases where users would manually modify their migrations, or squash multiple migrations together, resulting in corrupted dictionary access for cases where a table was deleted and created in the same migration.
This fix just makes the temporal information map handling more robust, so those errors don't happen anymore. There is a broader issue with temporal tables but those will be addressed in a different PR.

Fixes #35162
@maumar
Copy link
Contributor

maumar commented Mar 12, 2025

reopening for potential patch

@maumar maumar reopened this Mar 12, 2025
@maumar maumar modified the milestones: 10.0.0, 9.0.x Mar 12, 2025
maumar added a commit that referenced this issue Mar 12, 2025
…Table Causes Issues with Subsequent Table Recreation

Port of #35764

Fixes #35162

Description
Problem was that in 9.0 we changed how we process temporal tables, tracking the temporal table information as we process migrations. However, we didn't take into account the cases where users would manually modify their migrations, or squash multiple migrations together, resulting in corrupted dictionary access for cases where a table was deleted and created in the same migration. This fix just makes the temporal information map handling more robust, so those errors don't happen anymore. There is a broader issue with temporal tables but those will be addressed in a different PR.

Customer impact
Some manually modified/squashed migrations no longer work when they are being applied, throwing an unhelpful exception (The given key '...' was not present in the dictionary.'). No good workaround apart from breaking the migrations apart.

How found
Reported by multiple customers on 9.

Regression
Yes.

Testing
Added infra to test these types of scenarios (containing intermediate migration state), added multiple tests both for regular and temporal tables.

Risk
Low - Very targeted fix, accessing the temporal information dictionary in a more robust way. No quirk, since migrations are design time, so hard to set switches for them.
@maumar maumar marked this as a duplicate of #35323 Mar 12, 2025
maumar added a commit that referenced this issue Mar 13, 2025
…Table Causes Issues with Subsequent Table Recreation (#35776)

Port of #35764

Fixes #35162

Description
Problem was that in 9.0 we changed how we process temporal tables, tracking the temporal table information as we process migrations. However, we didn't take into account the cases where users would manually modify their migrations, or squash multiple migrations together, resulting in corrupted dictionary access for cases where a table was deleted and created in the same migration. This fix just makes the temporal information map handling more robust, so those errors don't happen anymore. There is a broader issue with temporal tables but those will be addressed in a different PR.

Customer impact
Some manually modified/squashed migrations no longer work when they are being applied, throwing an unhelpful exception (The given key '...' was not present in the dictionary.'). No good workaround apart from breaking the migrations apart.

How found
Reported by multiple customers on 9.

Regression
Yes.

Testing
Added infra to test these types of scenarios (containing intermediate migration state), added multiple tests both for regular and temporal tables.

Risk
Low - Very targeted fix, accessing the temporal information dictionary in a more robust way. No quirk, since migrations are design time, so hard to set switches for them.
@roji roji removed the closed-fixed label Mar 17, 2025
@Dennis-N8
Copy link

Was waiting for the fix, but it probably wasn't in today's Visual Studio update (still get that error).

@maumar maumar modified the milestones: 9.0.x, 9.0.5 Mar 27, 2025
@maumar
Copy link
Contributor

maumar commented Mar 27, 2025

@Dennis-N8 the fix will be available for EF Core 10 Preview 3 and EF Core 9.0.5

@maumar maumar closed this as completed Mar 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment