Migrations: deleting an index with a long and truncated name results in renaming collision
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- PR merge metrics
- PR metrics pending
Description
Consider a model where you have very long property names. Also, imagine that you are using a database where identifiers have a limited length, so long names are truncated (like Postgres). If you delete an index, then the migration will try to rename the other existing indices to take its place in the truncation order. However, the rename sequence must be done in a particular order. Otherwise, the renaming process will result in collisions. It appears that in EF Core, these renames are done in the opposite order. This may be easier to understand by looking at the code below. (I am not sure whether to file is in the `EF Core` repo vs `Npgsql.EFCore` repo. If `Npgsql` is more appropriate, please let me know and I'll move it.)
How to repro:
1. Use the code below.
2. Create `MyFirstMigration`
3. Comment out the first index from `OnModelCreating()`
4. Create `MySecondMigration`
5. Update database
```C#
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
class Program { static void Main(string[] args) { } }
public class BloggingContext : DbContext
{
public DbSet BlogPosts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql($"Host=localhost;Integrated Security=True;Username=postgres;" +
$"Database=DeleteIndexTest;Include Error Detail=True");
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// comment this statement out
modelBuilder.Entity()
.HasIndex(p => new { p.VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongName1 });
modelBuilder.Entity()
.HasIndex(p => new { p.VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongName2 });
modelBuilder.Entity()
.HasIndex(p => new { p.VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongName3 });
}
}
public class BlogPost
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongName1 { get; set; }
public int VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongName2 { get; set; }
public int VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongName3 { get; set; }
}
```
### Contents of `MySecondMigration.cs`
```C#
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DeleteIndexMigrationTest.Migrations
{
///
public partial class MySecondMigration : Migration
{
///
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryL~",
table: "BlogPosts");
// if this were reversed with the statement below, then the problem is fixed
migrationBuilder.RenameIndex(
name: "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVery~2",
table: "BlogPosts",
newName: "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVery~1");
// if this were reversed with the statement above, then the problem is fixed
migrationBuilder.RenameIndex(
name: "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVery~1",
table: "BlogPosts",
newName: "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryL~");
}
///
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameIndex(
name: "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryL~",
table: "BlogPosts",
newName: "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVery~1");
migrationBuilder.RenameIndex(
name: "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVery~1",
table: "BlogPosts",
newName: "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVery~2");
migrationBuilder.CreateIndex(
name: "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryL~",
table: "BlogPosts",
column: "VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongName1");
}
}
}
```
### Include verbose output
```
PM> Add-Migration MyFirstMigration
Build started...
Build succeeded.
To undo this action, use Remove-Migration.
PM> Add-Migration MySecondMigration
Build started...
Build succeeded.
To undo this action, use Remove-Migration.
PM> Update-Database
Build started...
Build succeeded.
Applying migration '20220520235844_MyFirstMigration'.
Applying migration '20220520235939_MySecondMigration'.
Failed executing DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
ALTER INDEX "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVery~2" RENAME TO "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVery~1";
Npgsql.PostgresException (0x80004005): 42P07: relation "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVery~1" already exists
at Npgsql.Internal.NpgsqlConnector.g__ReadMessageLong|213_0(NpgsqlConnector connector, Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
at Npgsql.NpgsqlDataReader.NextResult()
at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteNonQuery(Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlCommand.ExecuteNonQuery()
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteNonQuery(RelationalCommandParameterObject parameterObject)
at Microsoft.EntityFrameworkCore.Migrations.MigrationCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.UpdateDatabase(String targetMigration, String connectionString, String contextType)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabaseImpl(String targetMigration, String connectionString, String contextType)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabase.<>c__DisplayClass0_0.<.ctor>b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
Exception data:
Severity: ERROR
SqlState: 42P07
MessageText: relation "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVery~1" already exists
File: tablecmds.c
Line: 3833
Routine: RenameRelationInternal
42P07: relation "IX_BlogPosts_VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVery~1" already exists
```
### Include provider and version information
EF Core version: 7.0.0-preview.4
Database provider: Postgresql
Target framework: .NET 6.0
Operating system: Windows 11 Pro 21H2
IDE: Microsoft Visual Studio 2022 17.2.0
Contributor guide
Assessment
This issue has not been assessed yet.