Search code examples
c#asp.net-coreentity-framework-coreasp.net-core-mvc

EF Core randomly renames a column, despite no changes to model


In my ASP.Net Core MVC app, I have a simple Game class, plus 2 GameRequest and GameResponse classes (in snippets below) that are used for making requests, or for displaying on a view.

On adding a new migration (for an unrelated feature), I noticed EF Core adding a .RenameColumn() operation, specifically from Games.NumberCodeDisplay -> Games.NumberCode, which makes no sense. The Games table never had the NumberCodeDisplay column in the first place, even in the previous migration where it was created.

Curiously, this incorrect column name is the same as that of GameResponse's despite it not being in the app's DbContext.

// GameModels.cs

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

    public string Name { get; set; } = string.Empty;
    public string NameCode { get; set; } = string.Empty;
    public decimal NumberCode { get; set; } // <--- this property has *never* changed
    public int YearReleased { get; set; }
}

public class GameRequest
{
    public string Name { get; set; } = string.Empty;
    public string NameCode { get; set; } = string.Empty;
    public decimal NumberCode { get; set; }
    public int YearReleased { get; set; }
}

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

    public string Name { get; set; } = string.Empty;
    public string NameCode { get; set; } = string.Empty;
    public string NumberCodeDisplay { get; set; } = string.Empty;
    public int YearReleased { get; set; }

    public static string GetNumberCodeDisplay(decimal numberCode)
    {
        // ...
    }
}
// migration file generated after `add-migration` 

public partial class AddField_Game_CoverImageUrl : Migration
{
    /// <inheritdoc />
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.RenameColumn(
            name: "NumberCodeDisplay",
            table: "Games",
            newName: "NumberCode");
    }

    /// <inheritdoc />
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.RenameColumn(
            name: "NumberCode",
            table: "Games",
            newName: "NumberCodeDisplay");
    }
}
// AppDbContext.cs

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }

    public DbSet<Song> Songs { get; set; } = default!;
    public DbSet<Game> Games { get; set; } = default!;
}

Solution

  • I found the problem, though I don't really know what caused it in the first place.

    I forgot to add the previous migration files where I created the table (let's call it Migration_1; the empty migration in the post would be Migration_2), and the model snapshot file. But what I've found is that:

    • The C# model class is always correct (Game has a field called NumberCode, never has it been NumberCodeDisplay)
    • I added the Migration_1 migration. The resulting migration file [timestamp]_Migration_1.cs is correct:
            protected override void Up(MigrationBuilder migrationBuilder)
            {
                migrationBuilder.CreateTable(
                    name: "Games",
                    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),
                        NameCode = table.Column<string>(type: "nvarchar(max)", nullable: false),
                        NumberCode = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
                        YearReleased = table.Column<int>(type: "int", nullable: false)
                    },
                    constraints: table =>
                    {
                        table.PrimaryKey("PK_Games", x => x.Id);
                    });
            }
    
    • However, the migration designer file ([timestamp]_Migration_1.Designer.cs) for some reason incorrectly specified that a Games table with the NumberCodeDisplay column should be created (before being applied to the actual database):
        partial class Add_Game
        {
            /// <inheritdoc />
            protected override void BuildTargetModel(ModelBuilder modelBuilder)
            {
    #pragma warning disable 612, 618
                modelBuilder
                    .HasAnnotation("ProductVersion", "7.0.7")
                    .HasAnnotation("Relational:MaxIdentifierLength", 128);
    
                SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
    
                modelBuilder.Entity("MyProject.Models.Game", b =>
                    {
                        b.Property<int>("Id")
                            .ValueGeneratedOnAdd()
                            .HasColumnType("int");
    
                        SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
    
                        b.Property<string>("Name")
                            .IsRequired()
                            .HasColumnType("nvarchar(max)");
    
                        b.Property<string>("NameCode")
                            .IsRequired()
                            .HasColumnType("nvarchar(max)");
    
                        b.Property<decimal>("NumberCodeDisplay") // <------ Here
                            .HasColumnType("decimal(18,2)");
    
                        b.Property<int>("YearReleased")
                            .HasColumnType("int");
    
                        b.HasKey("Id");
    
                        b.ToTable("Games");
                    });
                // ...
            }
        }
    
    
    • But in the database itself, the Games table does have the correct NumberCode column name. enter image description here

    This is probably due to me adding/removing some migrations after Migration_1, notably there were a couple "Migration_2s" before I decided to cut out all the model changes (for an unrelated feature) and test out an empty migration (the current Migration_2). But I can't be sure.

    For now, I've just manually edited Migration_1's designer file, and the model snapshot file, and then further migrations wouldn't have the rename again.