1

I'm trying to map some enums in my model to PostgreSQL custom enum types using EF Core, but no matter what I do, EF Core keeps sending the values as integers instead of using the PostgreSQL enum type.

Enum definition:

public enum EventType
{
    Purchase,
    Signup,
    OfferTrading
}

DbContext configuration:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);

    modelBuilder.HasPostgresEnum<EventType>(
        name: "event_type",
        nameTranslator: new NpgsqlSnakeCaseNameTranslator());

    // also tried:
    // modelBuilder.HasPostgresEnum<EventType>("event_type");
    // modelBuilder.HasPostgresEnum<EventType>();

    base.OnModelCreating(modelBuilder);
}

NpgsqlDataSource configuration:

var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);

dataSourceBuilder.MapEnum<EventType>(
    pgName: "event_type",
    nameTranslator: new NpgsqlSnakeCaseNameTranslator());

// also tried:
// dataSourceBuilder.MapEnum<EventType>("event_type");
// dataSourceBuilder.MapEnum<EventType>();

var dataSource = dataSourceBuilder.Build();

services.AddSingleton(dataSource);

services.AddDbContext<AppDbContext>((sp, options) =>
    options.UseNpgsql(
            sp.GetRequiredService<NpgsqlDataSource>(),
            b => b.MigrationsAssembly("*******.Infrastructure"))
        .UseSnakeCaseNamingConvention()
        .EnableSensitiveDataLogging()
        .EnableDetailedErrors()
        .AddInterceptors(new AuditInterceptor()));

Design-time configuration

I also implemented IDesignTimeDbContextFactory<> with the same configuration.

Entity configuration:

builder.Property(c => c.EventType)
    .HasColumnType("event_type")
    .IsRequired();

I also tried adding a string converter here, but that only changes the error from integer to text. It still doesn't cast to the event_type PostgreSQL type.

Migration

From the initial migration, I can see that the enum type is created correctly (and I confirmed it exists in the database):

migrationBuilder.AlterDatabase()
    .Annotation("Npgsql:Enum:event_type", "purchase,signup,offer_trading");

And in the table definition:

event_type = table.Column<int>(type: "event_type", nullable: false),

Generated SQL

When inserting data, EF Core generates the following SQL:

INSERT INTO campaigns (id, created_at, event_type, external_template_id, name, partner_id, priority, updated_at, validity_period_end, validity_period_start, contact_channels)
VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10)

Parameters:

..., @p2 = '0', ...
// should be something like:
@p2 = 'purchase' (DbType = event_type)

It is clearly not setting the parameter type to event_type. It just sends an integer value.

If I use a string converter, it sends a string instead, but still does not set the correct PostgreSQL enum type, and the error persists.

Final error

PostgresException: 42804: column "event_type" is of type event_type but expression is of type integer

Versions used:

  • .NET 10
  • EF Core 10
  • Npgsql.EntityFrameworkCore.PostgreSQL 10.0.1
  • Npgsql 10.0.2
  • PostgreSQL 18

There are no global enum conversions configured (I previously had one, but removed it).

Question

What am I missing for EF Core + Npgsql to correctly bind the enum as a PostgreSQL enum type instead of sending it as integer or text?

Any help would be greatly appreciated.

1 Answer 1

3

Found the solution here.

In EF 9+, you only need to configure the mapping in one place:

NpgsqlDataSource configuration:

var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);

// you don't need this anymore:
// dataSourceBuilder.MapEnum<EventType>(
//     pgName: "event_type",
//     nameTranslator: new NpgsqlSnakeCaseNameTranslator());

var dataSource = dataSourceBuilder.Build();

services.AddSingleton(dataSource);

services.AddDbContext<AppDbContext>((sp, options) =>
    options.UseNpgsql(
            sp.GetRequiredService<NpgsqlDataSource>(), b => 
                b.MigrationsAssembly("*******.Infrastructure"))
                // instead, you configure it here:
                b.MapEnum<EventType>("event_type", nameTranslator: new NpgsqlSnakeCaseNameTranslator());
        .UseSnakeCaseNamingConvention()
        .EnableDetailedErrors()
        .AddInterceptors(new AuditInterceptor()));

And that's it. You don't need to configure it again inside your DbContext's modelBuilder, like in:

// remove this:
// modelBuilder.HasPostgresEnum<EventType>();

You also don't need to specify HasColumnType:

builder.Property(c => c.EventType)
    // also remove this:
    // .HasColumnType("event_type")
    .IsRequired();

On a side note, you also shouldn't create a new NpgsqlSnakeCaseNameTranslator instance each time the context is instantiated, assign a readonly variable to it and use it as many times you want. The same goes for any interceptors, like my AuditInterceptor above, register it as Scoped and use DI. Or else it will cause issues like:

An error was generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.ManyServiceProvidersCreatedWarning': More than twenty 'IServiceProvider' instances have been created for internal use by Entity Framework. This is commonly caused by injection of a new singleton service instance into every DbContext instance. For example, calling 'UseLoggerFactory' passing in a new instance each time--see https://go.microsoft.com/fwlink/?linkid=869049 for more details. This may lead to performance issues, consider reviewing calls on 'DbContextOptionsBuilder' that may require new service providers to be built. This exception can be suppressed or logged by passing event ID 'CoreEventId.ManyServiceProvidersCreatedWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.