Search code examples
c#entity-frameworkenumsmigrationnpgsql

EF Core migration creates incorrect enums


What can be the reason that EF Core can incorrectly read db context and set wrong enum types for database?

I had DB-First and scaffolded context and entities in my project. After it was found out there are unmapped enums in my db and my context, I created enums in Pascal case and mapped them to tables like this:

modelBuilder
    .HasPostgresEnum<AutoStatus>()
    .HasPostgresEnum<AnotherEnum>()
    .HasPostgresEnum<AndAnotherEnum>();

modelBuilder.Entity<Auto>(entity =>
    entity.Property(e => e.Status).HasColumnName("status")
        .HasConversion<string>()
        .HasDefaultValueSql(AutoStatus.InAssembly.ToString())
        .HasColumnType(nameof(AutoStatus));
);

Next was created an "Initial" migration and I tried to update (create) database by this migration. But EF spat out error "42704: type "autostatus" does not exist".

So, migration, migration designer and model snapshot had another enums for some reason:

  • Migration:
migrationBuilder.AlterDatabase()
    .Annotation("Npgsql:Enum:auto_status", "in_assembly,ready_to_test,in_test,ready_to_sale,sold")
  • Migration Designer:
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "auto_status", new[] { "in_assembly", "ready_to_test", "in_test", "ready_to_sale", "sold" });
  • Context Snapshot NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "auto_status", new[] { "in_assembly", "ready_to_test", "in_test", "ready_to_sale", "sold" });

Why still there are old enums, if nowhere in project aren't uses of old (snakecase) enums?


Solution

  • So, the solution was to call MapEnum onto NpgsqlDataSourceBuilder:

    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
    var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
    dataSourceBuilder.MapEnum<AutoStatus>("auto_status");
    var dataSource = dataSourceBuilder.Build();
    builder.Services.AddDbContext<AutoDealerContext>(options => options.UseNpgsql(dataSource));
    

    Enum:

    public enum AutoStatus
    {
        InAssembly,
        ReadyToTest,
        InTest,
        ReadyToSale
        Sold
    }
    

    OnModelCreating:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.HasPostgresEnum<AutoStatus>();
    }
    

    It is also important to note, that call .HasConversion<string>() unecessary:

    modelBuilder.Entity<Auto>(entity =>
    {
        entity.Property(e => e.Status)
            .HasColumnName("status")
            .HasDefaultValueSql("'in_assembly'");
    }