npgsql / efcore.pg

Entity Framework Core provider for PostgreSQL
PostgreSQL License
1.59k stars 227 forks source link

Incorrect C# enums mapping to postgres enum with non-default schema #2963

Open iPilot opened 1 year ago

iPilot commented 1 year ago

On enum mapping to db type with non-default schema, enum type is created correctly, but columns of that type are mapped to db type with default schema, so update-database command leads to error "type "" does not exist"

  /// <inheritdoc />
  public partial class Initial : Migration
  {
      /// <inheritdoc />
      protected override void Up(MigrationBuilder migrationBuilder)
      {
          migrationBuilder.EnsureSchema(
              name: "enum_schema");

          migrationBuilder.AlterDatabase()
              .Annotation("Npgsql:Enum:test_enum", "first,second,third")
              .Annotation("Npgsql:Enum:test_enum2", "n1,n2,n3");

          migrationBuilder.CreateTable(
              name: "TestEntities",
              schema: "enum-schema",
              columns: table => new
              {
                  Id = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
                  Name = table.Column<string>(type: "text", nullable: false),
                  Enum = table.Column<TestEnum>(type: "test_enum", nullable: false),
                  Enum2 = table.Column<TestEnum2>(type: "test_enum2", nullable: false)
              },
              constraints: table =>
              {
                  table.PrimaryKey("PK_TestEntities", x => x.Id);
              });
      }

      /// <inheritdoc />
      protected override void Down(MigrationBuilder migrationBuilder)
      {
          migrationBuilder.DropTable(
              name: "TestEntities",
              schema: "enum-schema");
      }
  }

Although the annotation creation command does not contain the schema, it is implicitly applied for an enum type, but not for a column that has that type.

CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
    "MigrationId" character varying(150) NOT NULL,
    "ProductVersion" character varying(32) NOT NULL,
    CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId")
);

START TRANSACTION;

DO $EF$
BEGIN
    IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'enum_schema') THEN
        CREATE SCHEMA enum_schema;
    END IF;
END $EF$;

DO $EF$
BEGIN
    IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'enum_schema') THEN
        CREATE SCHEMA enum_schema;
    END IF;
END $EF$;

CREATE TYPE enum_schema.test_enum AS ENUM ('first', 'second', 'third');
DO $EF$
BEGIN
    IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'enum_schema') THEN
        CREATE SCHEMA enum_schema;
    END IF;
END $EF$;

CREATE TYPE enum_schema.test_enum2 AS ENUM ('n1', 'n2', 'n3');

CREATE TABLE enum_schema."TestEntities" (
    "Id" numeric(20,0) NOT NULL,
    "Name" text NOT NULL,
    "Enum" test_enum NOT NULL,
    "Enum2" test_enum2 NOT NULL,
    CONSTRAINT "PK_TestEntities" PRIMARY KEY ("Id")
);

INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ('20231119201535_Initial', '7.0.13');

COMMIT;

Also, ensure schema is triggered 3 times - every HasPostgresEnum adds to migration script schema creation command even when the type's schema equals to a default database schema.

iPilot commented 1 year ago

https://github.com/iPilot/NpgSqlEnumBug

Repro repo (see b1531bd7e690485da0bc96ab00d51723db1674ee @ master)

iPilot commented 1 year ago

After few iterations I've figured out that column type mapping also depends on NpgSqlDatasourceBuilder.MapEnum<T> but there is still inconsistency:

Workaround is to call NpgSqlDatasourceBuilder.MapEnum<T>(${schema}.\"{typeName}\") explicitly, but it requires to explicitly configure type name in HasPostgresEnum<T> call. If default calls of HasPostgresEnum<T> and MapEnum<T> (with no arguments) are used for configuration, but database default schema is overridden, then exception will be thrown on database update.