Npgsql EF Core不能把枚举映射到PostgreSQL的枚举类型,而是以整数形式发送
我正尝试在我的模型中把一些枚举映射到PostgreSQL的自定义枚举类型,使用EF Core,但无论我怎么做,EF Core总是把值作为整数发送,而不是使用PostgreSQL的枚举类型。
枚举定义:
public enum EventType
{
Purchase,
Signup,
OfferTrading
}
DbContext 配置:
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 配置:
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()));
设计时配置
我也用同样的配置实现了 IDesignTimeDbContextFactory<>。
实体配置:
builder.Property(c => c.EventType)
.HasColumnType("event_type")
.IsRequired();
我也试过在这里添加一个字符串转换器,但那只是把错误从 integer 改成了 text。它仍然不能把值强制转换为 event_type PostgreSQL类型。
迁移
从初始迁移来看,枚举类型被正确创建(我也确认它在数据库中存在):
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:event_type", "purchase,signup,offer_trading");
在表定义中:
event_type = table.Column<int>(type: "event_type", nullable: false),
生成的SQL
在插入数据时,EF Core生成了以下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)
参数:
..., @p2 = '0', ...
// should be something like:
@p2 = 'purchase' (DbType = event_type)
显然它并没有把参数类型设置为 event_type。它只是发送一个整数值。
如果使用字符串转换器,它会发送一个字符串,但仍然没有设置正确的PostgreSQL枚举类型,错误依旧存在。
最终错误
PostgresException: 42804: 列 "event_type" 的类型是event_type,但表达式的类型是integer
使用的版本:
- .NET 10
- EF Core 10
- Npgsql.EntityFrameworkCore.PostgreSQL 10.0.1
- Npgsql 10.0.2
- PostgreSQL 18
没有配置全局枚举转换(我之前有一个,但已移除)。
问题
我还缺少什么,使EF Core + Npgsql能正确把枚举绑定为PostgreSQL枚举类型,而不是以整数或文本发送?
非常感谢任何帮助。
解决方案
在这里找到解决方案https://www.npgsql.org/efcore/mapping/enum.html?tabs=with-external-datasource%2Cwith-datasource
在EF 9+中,你只需要在一个地方配置映射:
NpgsqlDataSource 配置:
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()));
就这么简单。你无需在DbContext的 modelBuilder中再次配置,如下所示的:
// remove this:
// modelBuilder.HasPostgresEnum<EventType>();
你也不需要指定 HasColumnType:
builder.Property(c => c.EventType)
// also remove this:
// .HasColumnType("event_type")
.IsRequired();
另外需要说明的是,实例化上下文时不应每次都创建一个新的 NpgsqlSnakeCaseNameTranslator 实例,应该把它赋给一个只读变量并多次使用。拦截器也同理,例如上面的我的 AuditInterceptor,将其注册为Scoped并使用DI。否则会引发如下问题:
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'.