question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unexpected DropIndex() call generated in migration Up() method when dropping PK

See original GitHub issue

The 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 the AccidentalId 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:closed
  • Created 2 years ago
  • Reactions:1
  • Comments:14 (14 by maintainers)

github_iconTop GitHub Comments

1reaction
lauxjpncommented, Sep 23, 2021

@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 their Up()/Down() methods).

1reaction
rojicommented, Sep 23, 2021

@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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Entity Framework migration doesn't drop index constraint
So, when I add migration, I get the following SQL generated and ... when I removed hasIndex() from DbContext , EF dropped the...
Read more >
Database: Migrations - Laravel 5.5 - The PHP Framework ...
These options pre-fill the generated migration stub file with the specified table: ... The up method is used to add new tables, columns,...
Read more >
The Unexpected Find That Freed 20GB of Unused Index ...
How to free space without dropping indexes or deleting data. Every few months we get an alert from our database monitoring to warn...
Read more >
ALTER TABLE - MariaDB Knowledge Base
DROP COLUMN [IF EXISTS] DROP INDEX [IF EXISTS] DROP FOREIGN KEY [IF ... In order to drop the column, an explicit DROP PRIMARY...
Read more >
Unable to drop non-PK index because it is referenced in a ...
Because a foreign key can point to a primary key or a unique constraint, and whoever created that foreign key possibly created it...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found