UseNpgsql with DbDataSource and Mapped Enum creates column with type <int>, not the mapped enum type
See original GitHub issueI 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:
- Created 9 months ago
- Comments:8 (4 by maintainers)
With 7.0.2-ci.20230129T151307 (the fix for #2557 ) the demo project is working correctly, usage of GlobalTypeMapper can be skipped now. Thanks
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.