efcore.pg icon indicating copy to clipboard operation
efcore.pg copied to clipboard

Using MapEnum doesn't seem to work properly

Open Miggets7 opened this issue 8 months ago • 2 comments

I all,

I'm not sure wether I'm doing something wrong or this functionality is a bit broken, but I'm setting up my enums like this:

var dataSourceBuilder = new NpgsqlDataSourceBuilder(configuration.ConnectionString);
dataSourceBuilder.EnableDynamicJson();
var dataSource = dataSourceBuilder.Build();

services.AddDbContext<DCMContext>(options => options.UseNpgsql(dataSource,
    o => {
        o.MapEnum<DeviceType>();
        o.MapEnum<DispatchStatus>();
        o.MapEnum<DriverStatus>();
        o.MapEnum<FuelType>();
        o.MapEnum<PaymentStatus>();
        o.MapEnum<PaymentType>();
        o.MapEnum<RateType>();
        o.MapEnum<SettingRight>();
        o.MapEnum<SettingType>();
        o.MapEnum<Sex>();
        o.MapEnum<TransferStatus>();
        o.MapEnum<TripCancelReason>();
        o.MapEnum<TripScope>();
        o.MapEnum<TripStatus>();
    }));

And create a migration which results in this:

migrationBuilder.AlterDatabase()
    .Annotation("Npgsql:Enum:device_type", "android,ios")
    .Annotation("Npgsql:Enum:dispatch_status", "accepted,automatic,canceled,pending,rejected,timeout")
    .Annotation("Npgsql:Enum:driver_status", "available,on_break,on_shift,unavailable")
    .Annotation("Npgsql:Enum:fuel_type", "cng,diesel,electric,hydrogen,lpg,petrol,unknown")
    .Annotation("Npgsql:Enum:payment_status", "awaiting_method,completed,failed,pending,processing,refunded,requires_manual_intervention")
    .Annotation("Npgsql:Enum:payment_type", "bank_transfer,cash,credit_card,debit_card,invoice,online_payment")
    .Annotation("Npgsql:Enum:rate_type", "distance,starting_rate,time")
    .Annotation("Npgsql:Enum:setting_right", "admin,self")
    .Annotation("Npgsql:Enum:setting_type", "base64,bool,date,date_time,duration,float,integer,string,time")
    .Annotation("Npgsql:Enum:sex", "female,male,other,unknown")
    .Annotation("Npgsql:Enum:transfer_status", "canceled,completed,failed,in_progress,pending")
    .Annotation("Npgsql:Enum:trip_cancel_reason", "client_cancels,client_gone,contractor_unknown,date_time_wrong,double_booked,van_required")
    .Annotation("Npgsql:Enum:trip_scope", "combined,return,standard")
    .Annotation("Npgsql:Enum:trip_status", "accepted,canceled,canceled_requested,completed,declined,in_progress,in_transit,new,no_show,no_show_requested");

And the model snapshot:

NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "device_type", new[] { "android", "ios" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "dispatch_status", new[] { "accepted", "automatic", "canceled", "pending", "rejected", "timeout" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "driver_status", new[] { "available", "on_break", "on_shift", "unavailable" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "fuel_type", new[] { "cng", "diesel", "electric", "hydrogen", "lpg", "petrol", "unknown" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "payment_status", new[] { "awaiting_method", "completed", "failed", "pending", "processing", "refunded", "requires_manual_intervention" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "payment_type", new[] { "bank_transfer", "cash", "credit_card", "debit_card", "invoice", "online_payment" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "rate_type", new[] { "distance", "starting_rate", "time" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "setting_right", new[] { "admin", "self" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "setting_type", new[] { "base64", "bool", "date", "date_time", "duration", "float", "integer", "string", "time" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "sex", new[] { "female", "male", "other", "unknown" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "transfer_status", new[] { "canceled", "completed", "failed", "in_progress", "pending" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "trip_cancel_reason", new[] { "client_cancels", "client_gone", "contractor_unknown", "date_time_wrong", "double_booked", "van_required" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "trip_scope", new[] { "combined", "return", "standard" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "trip_status", new[] { "accepted", "canceled", "canceled_requested", "completed", "declined", "in_progress", "in_transit", "new", "no_show", "no_show_requested" });
...
b1.Property<Sex>("Sex")
    .HasColumnType("sex");

Now I'm getting this exception when retrieving data from the database:

System.InvalidCastException: Reading as 'DCM.Entities.Enums.Sex' is not supported for fields having DataTypeName 'public.sex'
 ---> System.NotSupportedException: Reading and writing unmapped enums requires an explicit opt-in; call 'EnableUnmappedTypes' on 'NpgsqlDataSourceBuilder' or NpgsqlConnection.GlobalTypeMapper....

And this is how the enum looks like:

public enum Sex
{
    Unknown,
    Male,
    Female,
    Other
}

As you can see the order is different, but I thought by reading through issue #3390 that it shouldn't matter. But still I'm getting above mentioned exception. Could somebody help me out?

Thank you

Miggets7 avatar Apr 11 '25 14:04 Miggets7

Basically with 9.0, if you are using NpgsqlDataSourceBuilder along with UseNpgsql, you need to add calls to MapEnum via your dataSourceBuilder as well. So keep your existing calls to MapEnum, but also add:

dataSourceBuilder.MapEnum<DeviceType>();
dataSourceBuilder.MapEnum<DispatchStatus> ();
...and so on

And that should solve your runtime error.

For a more detailed explanation of why this is required, see the Breaking Changes documentation for v9.0.

strawberry-fish13 avatar Jul 18 '25 14:07 strawberry-fish13

Apparently this is broken again in 10.0 RC 😮‍💨 https://github.com/npgsql/efcore.pg/issues/3653

reyou avatar Nov 07 '25 04:11 reyou