Visitar URL original
MapEnum interferes with previous ConfigureDataSource call · Issue #3478 · npgsql/efcore.pg · GitHub
Skip to content

MapEnum interferes with previous ConfigureDataSource call #3478

Closed

Description

@rudism

After upgrading from Npgsql 8 to 9 I was running into this error wherever I'm trying to select tuples from my database context:

System.InvalidCastException: Reading as 'System.ValueTuple`2[[System.Guid, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Int32, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]' is not supported for fields having DataTypeName 'record' ---> System.NotSupportedException: Could not read a PostgreSQL record. If you're attempting to read a record as a .NET tuple, call 'EnableRecordsAsTuples' on 'NpgsqlDataSourceBuilder' or NpgsqlConnection.GlobalTypeMapper (see https://www.npgsql.org/doc/types/basic.html and the 8.0 release notes for more details). If you're reading a record as a .NET object array using NpgsqlSlimDataSourceBuilder, call 'EnableRecords'.

This was despite calling .ConfigureDataSource(ds => ds.EnableRecordsAsTuples()) when setting up the context pool.

I managed to track it down to the fact that I am also calling MapEnum further down the configuration chain. If I remove the MapEnum calls, or move them so that those calls are made before the ConfigureDataSource call then the above error goes away. Here is a small webapi program that reproduces the problem:

Program.cs
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;

public class TestEntity {
  [Key]
  public int Id { get; set; }
  public required string First { get; set; }
  public required string Second { get; set; }
}

public class AppDbContext(
    DbContextOptions<AppDbContext> options) : DbContext(options) {
  public required DbSet<TestEntity> TestEntities { get; set; }
}

[DbContext(typeof(AppDbContext))]
[Migration("00_InitialSchema")]
public class InitialSchema : Migration {
  protected override void Up(MigrationBuilder migrationBuilder) {
    migrationBuilder.AlterDatabase()
      .Annotation("Npgsql:Enum:public.test_enum", "value_one,value_two");

    migrationBuilder.CreateTable(
      name: "TestEntities",
      columns: table => new {
        Id = table.Column<int>(type: "integer", nullable: false)
          .Annotation("Npgsql:ValueGenerationStrategy",
            NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
        First = table.Column<string>(type: "text", nullable: false),
        Second = table.Column<string>(type: "text", nullable: false)
      },
      constraints: table => {
        table.PrimaryKey("PK_TestEntities", x => x.Id);
      });
  }
}

public enum TestEnum {
  ValueOne,
  ValueTwo,
}

public class Program {
  public static void Main(string[] args) {
    var builder = WebApplication.CreateBuilder(args);
    builder.Services
      .AddDbContextPool<AppDbContext>(
        static (provider, builder) => builder
          .UseNpgsql(
            "Server=127.0.0.1;User Id=cosmic;Password=password;Database=postgres",
            static opt => opt
              .ConfigureDataSource(static ds => ds
                .EnableRecordsAsTuples())
              // commenting out the following line or moving it
              // before the ConfigureDataSource call fixes it
              .MapEnum<TestEnum>("test_enum", "public", null)
        ));
    var app = builder.Build();
    using var scope = app.Services.CreateScope();
    using var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    db.Database.Migrate();

    var newRow = new TestEntity {
      First = "foo",
      Second = "bar",
    };

    db.TestEntities.Add(newRow);
    db.SaveChanges();

    var tuple = db.Database.SqlQueryRaw<(string, string)>(@"
      select row(""First"", ""Second"") as ""Value""
      from ""TestEntities""").ToList();
  }
}

Activity

  1. roji commented on Feb 28, 2025

    @roji
    Member

    Confirmed.

    Cleaned-up repro
    await using var context = new AppDbContext();
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();
    
    var newRow = new TestEntity {
        First = "foo",
        Second = "bar",
    };
    
    context.TestEntities.Add(newRow);
    context.SaveChanges();
    
    var tuple = context.Database.SqlQueryRaw<(string, string)>(@"
          select row(""First"", ""Second"") as ""Value""
          from ""TestEntities""").ToList();
    
    public class TestEntity
    {
        [Key]
        public int Id { get; set; }
    
        public required string First { get; set; }
        public required string Second { get; set; }
    }
    
    public class AppDbContext : DbContext
    {
        public DbSet<TestEntity> TestEntities { get; set; }
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseNpgsql(
                "Host=localhost;Username=test;Password=test",
                static opt => opt
                    .ConfigureDataSource(static ds => ds.EnableRecordsAsTuples())
                    // commenting out the following line or moving it before the ConfigureDataSource call fixes it
                    .MapEnum<TestEnum>("test_enum", "public"));
        }
    }
    
    public enum TestEnum
    {
        ValueOne,
        ValueTwo,
    }
  2. self-assigned this
    on Feb 28, 2025
  3. added this to the 9.0.4 milestone on Feb 28, 2025
  4. added a commit that references this issue on Mar 1, 2025
    bd23fb0
  5. roji commented on Mar 1, 2025

    @roji
    Member

    Thanks for the issue and repro @rudism, I've found the problem and am pushing a fix for 9.0.4

  6. added 2 commits that reference this issue on Mar 1, 2025
    80ec4ae
    86f3474
  7. added theissue type on Jun 19, 2025
  8. sguryev commented on Jul 21, 2025

    @sguryev

    In case anybody will searching for EnableDynamicJson() doesn't work or MigrationsAssembly() resets ConfigureDataSource(). Same issue here

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions