-
Notifications
You must be signed in to change notification settings - Fork 254
Description
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();
}
}