question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

UseNpgsql with DbDataSource and Mapped Enum creates column with type <int>, not the mapped enum type

See original GitHub issue

I am using dotnet SDK 7.0.100, EF Core 7.0.1, Npgsql.EntityFrameworkCore.PostgreSQL 7.0.0 on Win10

In my project I am using C# enums and map them to Postgresql types. When upgrading from Npgsql 6 to Npgsql 7, there was a warning that: “‘NpgsqlConnection.GlobalTypeMapper’ is obsolete”. So I switched to DbDataSource. But when doing an initial migration step, the columns will not be created with the enum type, but with type int. Keeping the NpgsqlConnection.GlobalTypeMapper would do it correctly.

I created a demo project here: https://github.com/hmundle/EFNpgsqlEnumMapperIssue

Here are the core pieces:

public enum QuadrantType
{    
    Undefined = 0,    
    A = 1,    
    B = 2,    
    C = 3,    
    D = 4
}
public partial class User
{
    [Required]
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required, StringLength(50)]
    public string Name { get; set; } = "";

    [Column("quadrant")]
    [Required]
    [Comment("Quadrant identifier")]
    public QuadrantType Quadrant { get; set; }
}

EfStructures\PpasDbContext.cs

using Npgsql;

using System.Data.Common;
namespace Ppas.Dal.EfStructures;
public partial class PpasDbContext : DbContext
{
    public PpasDbContext(DbContextOptions<PpasDbContext> options)
        : base(options)
    { }
    public virtual DbSet<User>? Users { get; set; }

    public static void RegisterGlobalMappers()
    {
        Console.WriteLine("Going to call: NpgsqlConnection.GlobalTypeMapper.MapEnum<*>()");
        NpgsqlConnection.GlobalTypeMapper.MapEnum<QuadrantType>("ppas.quadrant_type");
    }
    public static DbDataSource BuildDataSource(string connectionString)
    {
        //RegisterGlobalMappers(); // uncomment this, to activate the GlobalTypeMapper
        // Create a data source with the configuration you want:
        var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
        dataSourceBuilder.MapEnum<QuadrantType>("ppas.quadrant_type");

        /*await using*/
        return dataSourceBuilder.Build();
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Fluent API calls go here
        modelBuilder.HasPostgresEnum<QuadrantType>("ppas");
        OnModelCreatingPartial(modelBuilder);
    }
    partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

EfStructures\PpasDbContextFactory.cs

using Microsoft.EntityFrameworkCore.Design;
namespace Ppas.Dal.EfStructures;
public class PpasDbContextFactory : IDesignTimeDbContextFactory<PpasDbContext>
{
    public PpasDbContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<PpasDbContext>();
        var connectionString = @"Host=localhost;Username=postgres;Port=5433;Password=DummyDummy;Database=TestDB";
        optionsBuilder.UseNpgsql(PpasDbContext.BuildDataSource(connectionString), x => x.MigrationsHistoryTable("ef_migrations_history", "migration"))
            .UseSnakeCaseNamingConvention();
        Console.WriteLine($"The connection string is: {connectionString}");
        return new PpasDbContext(optionsBuilder.Options);
    }
}

Configuration\DalConfiguration.cs I want to use for ASP.NET

...
public static IServiceCollection RegisterDalServices(this IServiceCollection services, string connectionString)
    {
        /*await using*/
        var dataSource = PpasDbContext.BuildDataSource(connectionString);
        services.AddDbContext<PpasDbContext>(
            options => options
                .UseNpgsql(dataSource, sqlOptions => sqlOptions.EnableRetryOnFailure().CommandTimeout(60))
                .UseSnakeCaseNamingConvention()
            );

        return services;
    }
...

When I am doing the initial migration step without NpgsqlConnection.GlobalTypeMapper.MapEnum I am getting:

...
migrationBuilder.AlterDatabase()
                .Annotation("Npgsql:Enum:ppas.quadrant_type", "undefined,a,b,c,d");

            migrationBuilder.CreateTable(
                name: "users",
                schema: "ppas",
                columns: table => new
                {
                    id = table.Column<int>(type: "integer", nullable: false)
                        .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
                    name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
                    quadrant = table.Column<int>(type: "integer", nullable: false, comment: "Quadrant identifier")
                },
                constraints: table =>
                {
                    table.PrimaryKey("pk_users", x => x.id);
                });
...

The issue here is, that the type of the quadrant column is <int>!

Repeating the same with NpgsqlConnection.GlobalTypeMapper.MapEnum:

            migrationBuilder.AlterDatabase()
                .Annotation("Npgsql:Enum:ppas.quadrant_type", "undefined,a,b,c,d");

            migrationBuilder.CreateTable(
                name: "users",
                schema: "ppas",
                columns: table => new
                {
                    id = table.Column<int>(type: "integer", nullable: false)
                        .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
                    name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
                    quadrant = table.Column<QuadrantType>(type: "ppas.quadrant_type", nullable: false, comment: "Quadrant identifier")
                },
                constraints: table =>
                {
                    table.PrimaryKey("pk_users", x => x.id);
                });

In this obsolet version 6 style the quadrant type is correct: <QuadrantType>!

Am I missing something, when switching to version 7?

This issue is similar to #2557, but this is focusing on the column type and not on the exception.

The complete source project as well as the resulting migration code can be found in https://github.com/hmundle/EFNpgsqlEnumMapperIssue

By the way, when downgrading this initial migration step, the type would not be cleaned up in both versions. You can see that already in the migration step code Down() method, it only drops the table. But that is supposed to be another issue.

Issue Analytics

  • State:closed
  • Created 9 months ago
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
hmundlecommented, Feb 5, 2023

With 7.0.2-ci.20230129T151307 (the fix for #2557 ) the demo project is working correctly, usage of GlobalTypeMapper can be skipped now. Thanks

1reaction
rojicommented, Jan 2, 2023

Thanks for filing the issue and providing code samples - I can see the bug and will work on patching it. In the meantime, continue using the global type mapper as before.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Data source enum mapping doesn't work · Issue #2557
UseNpgsql with DbDataSource and Mapped Enum creates column with type <int>, not the mapped enum type #2603.
Read more >
Enum Type Mapping
Creating your database enum. First, you must specify the PostgreSQL enum type on your model, just like you would with tables, sequences or...
Read more >
Entity framework core postgres enum type does not exist
I have a column in postgres of type enum (name of the enum/type in the database : document_validity ). Entity framework generates the...
Read more >
Npgsql type mapping. PostgreSQL has the unique feature of ...
Npgsql type mapping. PostgreSQL has the unique feature of supporting array data types. NET doesn't provide a standard spatial library, but NetTopologySuite ...
Read more >
How to use Enums when using Entity Framework Core with ...
Let's look at a couple of different ways to model enums, and how we can pre-populate our database with these values.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found