efcore.pg icon indicating copy to clipboard operation
efcore.pg copied to clipboard

Seeding database during migration fails when using newly added Enum

Open pgolinski opened this issue 1 month ago • 4 comments

I have a migration in EF Core 9 (package: Npgsql.EntityFrameworkCore.PostgreSQL 9.0.4) adding new enum type:

   migrationBuilder.AlterDatabase()
      .Annotation("Npgsql:Enum:event_stack_event_status", "closed,new,taken,unknown")

The enum is registered like so:

       services.AddDbContext<DatabaseContext>(options =>
       {
           options.UseNpgsql(
               CreateConnString(variables.DatabaseName),
               npsqlOptions =>
               {
                   npsqlOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery);
                   npsqlOptions.MapEnum<EventStackEventStatus>();
               });
           options.UseDataSeeding();
           options.ConfigureWarnings(c => c.Log((RelationalEventId.CommandExecuted, LogLevel.Debug)));
       }, ServiceLifetime.Transient);

The UseDataSeeding is my extension simply adding seeder implementation:

    public static void UseDataSeeding(this DbContextOptionsBuilder options)
    {
        options.UseSeeding(
            (ctx, storeManagementPerformed) => SeedAsync(ctx, storeManagementPerformed).Wait());

        options.UseAsyncSeeding(SeedAsync);
    }

Migration is applied via migration bundle. During the seed process, where the enum is used, I'm getting error:

System.NotSupportedException: The data type name 'event_stack_event_status' isn't present in your database. You may need to install an extension or upgrade to a newer version.

As far as I know this is an issue with type cache which doesn't contain new enum mapping. I tried to call ReloadTypes in data seeder on current database connection from database context but it changed error to:

System.InvalidCastException: Writing values of 'EventStackEventStatus' is not supported for parameters having DataTypeName 'public.event_stack_event_status'

I'm aware of this issue: #292 and PR that solving it #2951, but it's not applying here because seeder is called during Migrate method before ReloadTypes is called: https://github.com/dotnet/efcore/blob/main/src/EFCore.Relational/Migrations/Internal/Migrator.cs#L192

The only workaround I see right now is to move data seeding to application start. Is there a way to solve it so I can still use data seeding during migration?

pgolinski avatar Dec 02 '25 07:12 pgolinski

FWIW, I was able to get a successful migration with custom data type enums (on 9.0.3) by doing:

public class MigrationDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
    public MyDbContext CreateDbContext(string[] args)
    {
        // Build configuration
        var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", false)
            .AddJsonFile("appsettings.Development.json", true)
            .AddEnvironmentVariables()
            .Build();

        var connectionString = configuration.GetConnectionString("MigrationConnection");

        optionsBuilder.UseNpgsql(connectionString, options => {
            npgsqlOptions.MapEnum<UserRole>("user_role");
            npgsqlOptions.MapEnum<AuthTypes>("auth_types");
        })
        .UseSeeding((dbContext, _) =>
        {
            SeedTestDataAsync(dbContext).GetAwaiter().GetResult();
        })
        .UseAsyncSeeding(async (dbContext, _, cancellationToken) =>
        {
            await SeedTestDataAsync(dbContext, cancellationToken);
        });
    }

    private async Task SeedTestDataAsync(DbContext dbContext, CancellationToken cancellationToken = default)
    {
        var conn = dbContext.Database.GetDbConnection();
        if (conn is NpgsqlConnection npgsqlConn)
        {
            try
            {
                await npgsqlConn.ReloadTypesAsync(cancellationToken);
            }
            catch
            {
                Console.WriteLine(
                    "Failed to reload Postgres types. This will likely cause issues with seeding test data.");
            }
        }
        // seed rows here...
    }
}

dben avatar Dec 02 '25 14:12 dben

Did the seeding adds these enums? I've done exactly that and have got InvalidCastException as mentioned in my post. I don't use design time factory, but does it make any difference? How do you run migrations?

pgolinski avatar Dec 02 '25 20:12 pgolinski

The migration did add the enums - they are present in the migration as

            migrationBuilder.AlterDatabase()
                .Annotation("Npgsql:Enum:auth_types", "api_key,basic_auth,o_auth")
                .Annotation("Npgsql:Enum:user_role", "admin,user");

then later on are used in the seed data rows.

I am using dotnet ef migrations bundle to generate a bundler for the actual deployments (that gets run in CI), but it is also working locally for me in aspire / command line with dotnet ef database update

dben avatar Dec 02 '25 20:12 dben

I should provide more debugging information but I don't know what. At least for now. I will try to do it once again.

pgolinski avatar Dec 03 '25 09:12 pgolinski