Skip to content

Instantly share code, notes, and snippets.

@Espleth
Created November 7, 2024 20:33
Show Gist options
  • Save Espleth/60c7f53a663e3b594d1757a131fa049a to your computer and use it in GitHub Desktop.
Save Espleth/60c7f53a663e3b594d1757a131fa049a to your computer and use it in GitHub Desktop.
EF Core Code-first extension to migrate C# enums from ints in Postgres to PG enums
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.NameTranslation;
namespace Celestial.Database.Extensions;
public static class MigrationsExtensions
{
/// <summary>
/// Convert int column to PG enum column using c# enum which was previously mapped to this column
/// </summary>
public static void AlterIntToEnum<TEnum>(this MigrationBuilder migrationBuilder, string tableName, string columnName)
where TEnum : Enum
{
// Get TEnum name as postgres enum name (e.g. from "UserType" to "user_type")
var pgEnumType = NpgsqlSnakeCaseNameTranslator.ConvertToSnakeCase(typeof(TEnum).Name);
var tempColumnName = $"{columnName}_temp";
// Get list with all enum values
var allEnumValues = Enum.GetValues(typeof(TEnum)).Cast<TEnum>().ToList();
var defaultValue = allEnumValues.First();
// Add temp column to store PG enum values
migrationBuilder.AddColumn<TEnum>(
name: tempColumnName,
table: tableName,
type: pgEnumType,
nullable: false,
defaultValue: defaultValue);
var defaultPgValue = NpgsqlSnakeCaseNameTranslator.ConvertToSnakeCase(defaultValue.ToString());
// Write sql code for "CASE" statement to convert int to PG enums
var sqlCaseCode = string.Join(" ", allEnumValues.Select(x =>
$"WHEN {Convert.ToInt32(x)} THEN '{NpgsqlSnakeCaseNameTranslator.ConvertToSnakeCase(x.ToString())}'"));
// Update temporary columns with enum values
migrationBuilder.Sql($"UPDATE \"{tableName}\" " +
$"SET \"{tempColumnName}\" = CASE \"{columnName}\" " +
$"{sqlCaseCode} ELSE '{defaultPgValue}' " +
$"END::{pgEnumType};");
// Drop original column with int values
migrationBuilder.DropColumn(
name: columnName,
table: tableName);
// Rename temp column to original column name
migrationBuilder.RenameColumn(
name: tempColumnName,
table: tableName,
newName: columnName);
}
}
@M3ales
Copy link

M3ales commented May 15, 2025

For schema specific cases:

public static void AlterIntToEnum<TEnum>(this MigrationBuilder migrationBuilder, string tableName, string columnName, bool nullable = false, string? schema = null)
        where TEnum : Enum
    {
        // Get TEnum name as postgres enum name (e.g. from "UserType" to "user_type")
        var pgEnumType = NpgsqlSnakeCaseNameTranslator.ConvertToSnakeCase(typeof(TEnum).Name);

        if (schema is not null)
        {
            pgEnumType = schema + "." + pgEnumType;
        }

        var tempColumnName = $"{columnName}_temp";

        // Get list with all enum values
        var allEnumValues = Enum.GetValues(typeof(TEnum)).Cast<TEnum>().ToList();
        var defaultValue = NpgsqlSnakeCaseNameTranslator.ConvertToSnakeCase(
            allEnumValues.First().ToString()
            );

        // Add temp column to store PG enum values
        migrationBuilder.AddColumn<TEnum>(
            name: tempColumnName,
            table: tableName,
            type: pgEnumType,
            nullable: nullable,
            schema: schema,
            defaultValue: defaultValue);

        var defaultPgValue = NpgsqlSnakeCaseNameTranslator.ConvertToSnakeCase(defaultValue.ToString());

        // Write sql code for "CASE" statement to convert int to PG enums
        var sqlCaseCode = string.Join(" ", allEnumValues.Select(x =>
            $"WHEN {Convert.ToInt32(x)} THEN '{NpgsqlSnakeCaseNameTranslator.ConvertToSnakeCase(x.ToString())}'"));

        var qualifiedTableName = schema is null ? $"\"{tableName}\"" : $"{schema}.\"{tableName}\"";

        // Update temporary columns with enum values
        migrationBuilder.Sql($"UPDATE {qualifiedTableName} " +
                             $"SET \"{tempColumnName}\" = CASE \"{columnName}\" " +
                             $"{sqlCaseCode} ELSE '{defaultPgValue}' " +
                             $"END::{pgEnumType};");

        // Drop original column with int values
        migrationBuilder.DropColumn(
            name: columnName,
            schema: schema,
            table: tableName);

        // Rename temp column to original column name
        migrationBuilder.RenameColumn(
            name: tempColumnName,
            table: tableName,
            schema: schema,
            newName: columnName);
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment