Unexpected DropIndex() call generated in migration Up() method when dropping PK
See original GitHub issueThe original issue is being tracked by https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues/1348.
It contains sample code and instructions that reproduce the issue:
The issue now is the following call in your second migrations
Up()
method:migrationBuilder.DropIndex( name: "IX_tasks_workers_TrekkTemplateId", table: "tasks_workers");
This call shouldn’t be there, but appears to be generated by the EF Core differ for some inexplicable reason.
So as a quick workaround, remove the line from the
Up()
method, and your code should run fine.The following sample code reproduces the issue for MySQL and SQL Server:
using System; using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace IssueConsoleTemplate { public class IceCream { public int IceCreamId { get; set; } public ICollection<IceCream_IceCreamParlor> IceCreamIceCreamParlors { get; set; } } public class IceCreamParlor { public int IceCreamParlorId { get; set; } public ICollection<IceCream_IceCreamParlor> IceCreamIceCreamParlors{ get; set; } } public class IceCream_IceCreamParlor { #if !SECOND_MIGRATION public int AccidentalId { get; set; } // <-- Remove for the second migration #endif public int IceCreamId { get; set; } public int IceCreamParlorId { get; set; } public IceCream IceCream { get; set; } public IceCreamParlor IceCreamParlor { get; set; } } public class Context : DbContext { public DbSet<IceCream> IceCreams { get; set; } public DbSet<IceCreamParlor> IceCreamParlors { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { // optionsBuilder.UseMySql("server=127.0.0.1;port=3306;user=root;password=;database=Issue1348_02", new MySqlServerVersion(new Version(8, 0, 21))) optionsBuilder.UseSqlServer(@"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=Issue1348_02") .UseLoggerFactory( LoggerFactory.Create( configure => configure .AddConsole() .AddFilter(level => level >= LogLevel.Information))) .EnableSensitiveDataLogging() .EnableDetailedErrors(); } } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<IceCream_IceCreamParlor>( entity => { #if !SECOND_MIGRATION entity.HasKey(e => e.AccidentalId); // <-- Remove for second migration #else entity.HasKey(e => new {e.IceCreamId, e.IceCreamParlorId}); // <-- Add for second migration #endif // WORKAROUND: // Explicitly defining the index will prevent EF Core from dropping it later. // entity.HasIndex(e => e.IceCreamId); // This is never needed, because this index is never being dropped by EF Core. // entity.HasIndex(e => e.IceCreamParlorId); entity.HasOne(e => e.IceCream) .WithMany() .HasForeignKey(e => e.IceCreamId); entity.HasOne(e => e.IceCreamParlor) .WithMany() .HasForeignKey(e => e.IceCreamParlorId); }); } } internal static class Program { private static void Main(string[] args) { using var context = new Context(); context.Database.EnsureDeleted(); context.Database.EnsureCreated(); } } }
Run the following PowerShell command from the project directory, to generate the two migrations:
rmdir .\Migrations\ -Recurse -ErrorAction SilentlyContinue; dotnet build '-p:DefineConstants=DEBUG NET5_0 NET'; dotnet ef migrations add Initial --verbose --no-build; dotnet build '-p:DefineConstants=DEBUG NET5_0 NET SECOND_MIGRATION'; dotnet ef migrations add ChangePrimaryKey --verbose --no-build
In this sample code, the unexpected call, that drops the index that is unrelated to the primary key, is the following from the
..._ChangePrimaryKey.cs
file:protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.DropPrimaryKey( name: "PK_IceCream_IceCreamParlor", table: "IceCream_IceCreamParlor"); // // This call should not be here: // migrationBuilder.DropIndex( name: "IX_IceCream_IceCreamParlor_IceCreamId", table: "IceCream_IceCreamParlor"); migrationBuilder.DropColumn( name: "AccidentalId", table: "IceCream_IceCreamParlor"); migrationBuilder.AddPrimaryKey( name: "PK_IceCream_IceCreamParlor", table: "IceCream_IceCreamParlor", columns: new[] { "IceCreamId", "IceCreamParlorId" }); }
It tries to drop the index of the
IceCreamId
column, even though it is theAccidentalId
column that is being relieved from being the primary key, before it is later dropped completely.This does not happen if the index has been explicitly defined in the model.
This issue does not seem to have any negative side effects for SQL Server when the generated SQL drops the index, but MySQL is not as forgiving and returns an error if you try to drop an index, that is being used for an existing foreign key relationship.
So the scenario is, that an m:n-table was accidentally provided with an auto incrementing ID column as the PK by the user (first migration). This is being corrected by making the two FKs of the m:n-table the PK and dropping the now unneeded old PK/ID column (second migration).
The result is, that in addition to the expected statements, an unexpected DropIndex()
call is being generated for an index of the first of the two columns (the FK to the m-side of the m:n-relationship) that participate in the new PK.
Since the m-side of the new PK is independent of the old PK (the accidental ID column), removing the old PK should not have any side effects on the index of the m-side of the new PK.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:14 (14 by maintainers)
@roji No problem, I wasn’t really expecting a change (because there could be issues with changing the index drop/create order in general, when indices with the same name get dropped and created).
We will implement it in Pomelo (we are likely to do it in the
MigrationsModelDiffer
, because it is customized already, it is where the sorting happens anyway, and it preserves the user’s agency when it comes to customizing the order of migration operation execution in theirUp()
/Down()
methods).@lauxjpn we discussed this in triage, and the general consensus is that the EF migration operation ordering is correct, and that the MySQL provider should work around this MySQL-specific limitation. One way is to do it in the differ (which you’ve already customized), although a better way would be to do this in the migration SQL generator, i.e. detect the multiple operations occurring on the same table and ordering properly there. This is how table rebuilds were implemented in the Sqlite provider - that can serve as inspiration.