Entity Framework Core integration for StrEnum.Npgsql — maps StrEnum string enums to native Postgres enum types in EF Core models, migrations, and queries.
This is the EF wrapper, mirroring how the Npgsql ecosystem ships Npgsql.NetTopologySuite (raw driver) and Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite (EF wrapper) as separate packages. If you only need the ADO.NET driver path, use StrEnum.Npgsql directly.
Supports EF Core 8 – 10. Targets net8.0, net9.0, net10.0.
Install StrEnum.Npgsql.EntityFrameworkCore via the .NET CLI:
dotnet add package StrEnum.Npgsql.EntityFrameworkCore
(StrEnum.Npgsql is brought in transitively.)
StrEnum.Npgsql.EntityFrameworkCore lets you choose how Entity Framework stores your string enums in Postgres:
- as plain text columns (the default), or
- as native Postgres enum types created via
CREATE TYPE ... AS ENUM (...).
public class Sport: StringEnum<Sport>
{
public static readonly Sport RoadCycling = Define("ROAD_CYCLING");
public static readonly Sport MountainBiking = Define("MTB");
public static readonly Sport TrailRunning = Define("TRAIL_RUNNING");
}
public class Race
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public Sport Sport { get; private set; }
private Race() { }
public Race(string name, Sport sport)
{
Id = Guid.NewGuid();
Name = name;
Sport = sport;
}
}Call UseStringEnums() when configuring your DB context:
public class RaceContext: DbContext
{
public DbSet<Race> Races { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseNpgsql("Host=localhost;Database=BestRaces;Username=*;Password=*;")
.UseStringEnums();
}
}EF Core stores the Sport property in a text column. Running dotnet ef migrations add Init produces:
migrationBuilder.CreateTable(
name: "Races",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Sport = table.Column<string>(type: "text", nullable: false)
},
constraints: table => table.PrimaryKey("PK_Races", x => x.Id));Three things have to line up for parameters to bind cleanly to a native Postgres enum:
- The enum type must exist in the database (
CREATE TYPE ... AS ENUM). - Npgsql has to know the CLR type ↔ enum OID mapping at the wire.
- EF Core has to pick a
RelationalTypeMappingthat doesn't pin the parameter totext.
StrEnum.Npgsql.EntityFrameworkCore covers all three:
// 1. Wire-level binding (StrEnum.Npgsql)
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
dataSourceBuilder.MapStringEnum<Sport>();
await using var dataSource = dataSourceBuilder.Build();
// 2. EF Core hookup
public class RaceContext: DbContext
{
public RaceContext(NpgsqlDataSource dataSource) { _dataSource = dataSource; }
private readonly NpgsqlDataSource _dataSource;
public DbSet<Race> Races { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseNpgsql(_dataSource)
.UseStringEnums() // string enums recognised as scalars
.UseStringEnumsAsPostgresEnums(); // route mapped properties to the enum OID
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Race>();
// 3. Migrations + per-property column type
modelBuilder.MapStringEnumAsPostgresEnum<Sport>();
}
}The generated migration looks like this:
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:sport", "ROAD_CYCLING,MTB,TRAIL_RUNNING");
migrationBuilder.CreateTable(
name: "Races",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Sport = table.Column<Sport>(type: "sport", nullable: false)
},
constraints: table => table.PrimaryKey("PK_Races", x => x.Id));MapStringEnumAsPostgresEnum<TEnum>() does two things:
- Registers a Postgres enum type in the EF model — produces the
CREATE TYPEmigration. Labels are taken from the string enum's underlying values, in declaration order. - Walks all entity types and configures every property of type
TEnumto use that Postgres enum as its column type.
By default the Postgres enum name is the snake_cased CLR type name (Sport → sport). Override the name and schema if you need to:
modelBuilder.MapStringEnumAsPostgresEnum<Sport>(name: "sport_kind", schema: "races");
dataSourceBuilder.MapStringEnum<Sport>(name: "sport_kind", schema: "races");For fine-grained control over which properties map to a Postgres enum, call HasPostgresStringEnum<TEnum>() per property:
modelBuilder.HasPostgresStringEnum<Sport>(); // creates the CREATE TYPE migration
modelBuilder.Entity<Race>()
.Property(r => r.Sport)
.HasPostgresStringEnum<Sport>();The property-level call inherits the schema set on the model-level HasPostgresStringEnum, so you don't have to repeat it.
Both modes can coexist in the same context:
modelBuilder.MapStringEnumAsPostgresEnum<Sport>(); // Sport -> sport enum
// Country has no Postgres-enum mapping, so it stays as textCountry properties stay as text because UseStringEnums() is on the options builder.
EF Core translates LINQ operations on string enums into SQL:
var trailRuns = await context.Races
.Where(r => r.Sport == Sport.TrailRunning)
.ToArrayAsync();When Sport is mapped to a Postgres enum, the parameter is sent and compared as that enum type — no text casts, no 42804 errors.
var cyclingSports = new[] { Sport.MountainBiking, Sport.RoadCycling };
var cyclingRaces = await context.Races
.Where(r => cyclingSports.Contains(r.Sport))
.ToArrayAsync();The relational type-mapping plugin is modelled on EFCore.PG's own type-mapping source plugins; the wire-level work lives in StrEnum.Npgsql.
Copyright © 2026 Dmytro Khmara.
StrEnum is licensed under the MIT license.