Skip to content

StrEnum/StrEnum.Npgsql.EntityFrameworkCore

Repository files navigation

StrEnum.Npgsql.EntityFrameworkCore

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.

Installation

Install StrEnum.Npgsql.EntityFrameworkCore via the .NET CLI:

dotnet add package StrEnum.Npgsql.EntityFrameworkCore

(StrEnum.Npgsql is brought in transitively.)

Usage

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 (...).

Storing string enums as text

Defining a string enum and an entity

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;
    }
}

Wiring it up

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));

Storing string enums as Postgres enum types

Three things have to line up for parameters to bind cleanly to a native Postgres enum:

  1. The enum type must exist in the database (CREATE TYPE ... AS ENUM).
  2. Npgsql has to know the CLR type ↔ enum OID mapping at the wire.
  3. EF Core has to pick a RelationalTypeMapping that doesn't pin the parameter to text.

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:

  1. Registers a Postgres enum type in the EF model — produces the CREATE TYPE migration. Labels are taken from the string enum's underlying values, in declaration order.
  2. Walks all entity types and configures every property of type TEnum to use that Postgres enum as its column type.

Customising the Postgres enum name and schema

By default the Postgres enum name is the snake_cased CLR type name (Sportsport). 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");

Configuring individual properties

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.

Mixing both modes

Both modes can coexist in the same context:

modelBuilder.MapStringEnumAsPostgresEnum<Sport>();    // Sport -> sport enum
// Country has no Postgres-enum mapping, so it stays as text

Country properties stay as text because UseStringEnums() is on the options builder.

Querying

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();

Acknowledgements

The relational type-mapping plugin is modelled on EFCore.PG's own type-mapping source plugins; the wire-level work lives in StrEnum.Npgsql.

License

Copyright © 2026 Dmytro Khmara.

StrEnum is licensed under the MIT license.

About

String enum support for EF core with Npgsql

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors