Skip to content

InvalidCastException when reading PostgreSQL enum types with Npgsql 10.0 RC #3653

@reyou

Description

@reyou

Description

When querying entities that include PostgreSQL enum properties, EF Core throws an InvalidCastException indicating that reading as System.Int32 is not supported for enum fields. This configuration worked correctly in Npgsql 8.x but is broken in Npgsql 10.0 RC.

Error Details

System.InvalidCastException: Reading as 'System.Int32' is not supported for fields having DataTypeName 'public.recipe_difficulty'
Source: Npgsql

Inner Exception:
NotSupportedException: Reading and writing unmapped enums requires an explicit opt-in; call 'EnableUnmappedTypes' on 'NpgsqlDataSourceBuilder' or NpgsqlConnection.GlobalTypeMapper

Stack Trace:

at Npgsql.Internal.AdoSerializerHelpers.<GetTypeInfoForReading>g__ThrowReadingNotSupported|0_0(Type type, PgSerializerOptions options, PgTypeId pgTypeId, Exception inner)
at Npgsql.Internal.AdoSerializerHelpers.GetTypeInfoForReading(Type type, PgTypeId pgTypeId, PgSerializerOptions options)
at Npgsql.BackendMessages.FieldDescription.<GetInfoCore>g__GetInfoSlow|51_0(Type type, ColumnInfo& lastColumnInfo)
at Npgsql.BackendMessages.FieldDescription.GetInfoCore(Type type, ColumnInfo& lastColumnInfo)
at Npgsql.BackendMessages.FieldDescription.GetInfo(Type type, ColumnInfo& lastColumnInfo)
at Npgsql.NpgsqlDataReader.<GetInfo>g__Slow|133_0(ColumnInfo& , PgConverter& , Size& , Boolean& , <>c__DisplayClass133_0& )
at Npgsql.NpgsqlDataReader.GetInfo(Int32 ordinal, Type type, PgConverter& converter, Size& bufferRequirement, Boolean& asObject)
at Npgsql.NpgsqlDataReader.GetFieldValueCore[T](Int32 ordinal)
at Npgsql.NpgsqlDataReader.GetInt32(Int32 ordinal)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.<MoveNextAsync>d__20.MoveNext()

Environment

  • .NET Version: 10.0 (RC)
  • Npgsql: 10.0.0-rc.1
  • Npgsql.EntityFrameworkCore.PostgreSQL: 10.0.0-rc.2
  • Entity Framework Core: 10.0.0-rc.2.25502.107
  • PostgreSQL: 15.3 (Debian 15.3-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
  • Operating System: Windows 10

Configuration

Enum Definition

public enum RecipeDifficulty
{
    Easy = 0,
    Medium = 1,
    Hard = 2
}

PostgreSQL Enum Type

CREATE TYPE recipe_difficulty AS ENUM ('Easy', 'Medium', 'Hard');

DbContext Configuration

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // Register enum types
    modelBuilder.HasPostgresEnum<RecipeDifficulty>(schema: "public", name: "recipe_difficulty");
    
    // Configure entity property
    modelBuilder.Entity<Recipe>(entity =>
    {
        entity.Property(e => e.Difficulty)
            .HasColumnType("public.recipe_difficulty");
    });
}

Service Registration

// Register NpgsqlDataSource with enum mappings
builder.Services.AddSingleton(provider =>
{
    IConfiguration configuration = provider.GetRequiredService<IConfiguration>();
    string connectionString = configuration["POSTGRESQL:CONNECTIONSTRING"]!;

    NpgsqlDataSourceBuilder dataSourceBuilder = new(connectionString);
    dataSourceBuilder.MapEnum<RecipeDifficulty>(
        "recipe_difficulty",
        new NpgsqlNullNameTranslator());
    
    return dataSourceBuilder.Build();
});

// Register DbContext using the NpgsqlDataSource
builder.Services.AddDbContext<MyDbContext>((serviceProvider, options) =>
{
    NpgsqlDataSource dataSource = serviceProvider.GetRequiredService<NpgsqlDataSource>();
    options.UseNpgsql(dataSource, b => b.MigrationsAssembly("MyMigrationsAssembly"));
});

Model Snapshot

The enum is properly registered in the model snapshot:

NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "public", "recipe_difficulty", new[] { "Easy", "Medium", "Hard" });

// Property configuration
b.Property<RecipeDifficulty>("Difficulty")
    .HasColumnType("public.recipe_difficulty")
    .HasColumnName("difficulty");

Expected Behavior

EF Core should successfully read enum values from PostgreSQL enum columns and map them to the corresponding C# enum type.

Actual Behavior

EF Core attempts to read the enum as Int32, causing an InvalidCastException. The error message suggests calling EnableUnmappedTypes(), but this should not be necessary when enums are explicitly mapped via MapEnum().

Worked in Previous Version

This exact configuration worked correctly in:

  • Npgsql: 8.x
  • Npgsql.EntityFrameworkCore.PostgreSQL: 8.x

Attempted Solutions

  1. Added EnableUnmappedTypes() to NpgsqlDataSourceBuilder - Error persists
  2. Added EF-level MapEnum() in UseNpgsql options builder - Error persists
  3. Verified enum registration in OnModelCreating - Already present
  4. Verified property configuration in model snapshot - Already present
  5. Verified HasColumnType("public.recipe_difficulty") on property - Already present

Additional Context

  • The enum is properly mapped in NpgsqlDataSourceBuilder with appropriate name translators
  • The enum is registered in EF Core's model via HasPostgresEnum
  • The property is correctly configured with HasColumnType
  • The model snapshot includes the enum registration and property configuration
  • The error occurs when EF Core tries to materialize query results, not during query construction

Questions

  1. Is EnableUnmappedTypes() required even when enums are explicitly mapped via MapEnum() in Npgsql 10.0 RC?
  2. When using an external NpgsqlDataSource, are there additional configuration steps required that differ from Npgsql 8.x?
  3. Is there a breaking change in enum handling between Npgsql 8.x and 10.0 RC that requires different configuration?

Reproduction Steps

  1. Create a PostgreSQL enum type
  2. Create a C# enum matching the PostgreSQL enum
  3. Configure NpgsqlDataSource with MapEnum<T>()
  4. Register enum in EF Core with HasPostgresEnum<T>()
  5. Configure entity property with HasColumnType("public.enum_name")
  6. Query entities that include the enum property
  7. Exception occurs when EF Core tries to materialize results

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions