使用 efcore.pg 对枚举进行故障排除
Troubleshooting enums with efcore.pg
在与 Npgsql.EntityFrameworkCore.PostgreSQL
提供程序一起使用时,是否有解决 enum
问题的最佳实践?只是我不知道该去哪里找了。
以下是我如何注册它们:
public class Client
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public SexType SexType { get; set; }
}
public enum SexType
{
Male,
Female,
}
public class MedServiceDbContext : DbContext
{
public const string DatabaseSchema = "public";
public DbSet<Client> Clients { get; protected set; }
static MedServiceDbContext()
{
NpgsqlConnection.GlobalTypeMapper.MapEnum<SexType>($"{DatabaseSchema}.{NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator.TranslateTypeName(nameof(SexType))}", NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator);
//NpgsqlConnection.GlobalTypeMapper.MapEnum<SexType>();
}
private static void MapTypes(ModelBuilder modelBuilder)
{
modelBuilder
.HasPostgresEnum<SexType>(DatabaseSchema, NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator.TranslateTypeName(nameof(SexType)), NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator);
;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
MapTypes(modelBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder
.UseNpgsql(@"Host=localhost;Database=TestEnum;Username=postgres;Password=123")
.LogTo(Console.WriteLine, LogLevel.Trace)
.EnableSensitiveDataLogging()
;
public MedServiceDbContext()
{
}
}
这是迁移的样子:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:public.sex_type", "male,female");
migrationBuilder.CreateTable(
name: "Clients",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "text", nullable: false),
SexType = table.Column<SexType>(type: "public.sex_type", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Clients", x => x.Id);
});
}
在干净的复制项目中使用时,它可以完美运行。它也适用于测试。但是相同的代码(复制粘贴)在给我的主项目中不起作用:
System.InvalidOperationException: An error occurred while reading a database value for property 'Client.SexType'. The expected type was 'MedService.API.Database.Entities.Enums.SexType' but the actual value was of type 'MedService.API.Database.Entities.Enums.SexType'.
---> System.InvalidCastException: Can't cast database type public.sex_type to Int32
at Npgsql.Internal.TypeHandling.NpgsqlTypeHandler.ReadCustom[TAny](NpgsqlReadBuffer buf, Int32 len, Boolean async, FieldDescription fieldDescription)
at Npgsql.NpgsqlDataReader.GetFieldValue[T](Int32 ordinal)
at Npgsql.NpgsqlDataReader.GetInt32(Int32 ordinal)
at lambda_method256(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
--- End of inner exception stack trace ---
at lambda_method256(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
我检查了所有相关的代码,但唯一的区别是数据库上下文的创建方式。主项目使用 DI 的方式与测试项目相同,而 repro 直接创建上下文。所以从逻辑上讲,如果主项目和测试项目工作相同,它们的行为应该相同,但它们不是。
我检查了每个生成的 SQL 脚本、LINQ 表达式等。一切似乎都一样,但我一直收到此异常。
我还应该在哪里寻找问题?
我已确认 sex_type
已在主项目和复制项目中创建。它们是相同的(使用 DBeaver 检查)。不仅如此,数据库结构也完全相同。数据库本身是使用 dotnet ef database update
.
创建的
P.S。
不,ReloadTypes()
在这里也无济于事。已经检查过了。
使用的提供商版本:6.0.3
使用的 PostgreSQL 版本:14.2
UPDATE_01
做了一些实验,这个有效:
var connection = (Npgsql.NpgsqlConnection)_medServiceDbContext.Database.GetDbConnection();
connection.Open();
using (var cmd = new Npgsql.NpgsqlCommand("SELECT c.\"Id\", c.\"Name\", c.\"SexType\" FROM public.\"Clients\" as c", connection))
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetFieldValue<int>(0);
var surname = reader.GetFieldValue<string>(1);
var sex = reader.GetFieldValue<SexType>(2);
Console.WriteLine("{0}, {1}, {2}", id, surname, sex);
}
}
而这不是:
_medServiceDbContext.Clients.AsNoTracking().ToArray()
后者给出了上述异常 (Can't cast database type public.sex_type to Int32
)。
我的猜测是出于某种原因 EF 提供程序使用 GetInt32()
override of the NpgsqlDataReader
implementation instead of GetFieldValue<T>()
.
UPDATE_02
实际上我终于 assemble repro. It seems the issue is related to a usage of multiple different contexts targeting the same database connection. Described my findings here。
在与 Npgsql.EntityFrameworkCore.PostgreSQL
提供程序一起使用时,是否有解决 enum
问题的最佳实践?只是我不知道该去哪里找了。
以下是我如何注册它们:
public class Client
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public SexType SexType { get; set; }
}
public enum SexType
{
Male,
Female,
}
public class MedServiceDbContext : DbContext
{
public const string DatabaseSchema = "public";
public DbSet<Client> Clients { get; protected set; }
static MedServiceDbContext()
{
NpgsqlConnection.GlobalTypeMapper.MapEnum<SexType>($"{DatabaseSchema}.{NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator.TranslateTypeName(nameof(SexType))}", NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator);
//NpgsqlConnection.GlobalTypeMapper.MapEnum<SexType>();
}
private static void MapTypes(ModelBuilder modelBuilder)
{
modelBuilder
.HasPostgresEnum<SexType>(DatabaseSchema, NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator.TranslateTypeName(nameof(SexType)), NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator);
;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
MapTypes(modelBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder
.UseNpgsql(@"Host=localhost;Database=TestEnum;Username=postgres;Password=123")
.LogTo(Console.WriteLine, LogLevel.Trace)
.EnableSensitiveDataLogging()
;
public MedServiceDbContext()
{
}
}
这是迁移的样子:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:public.sex_type", "male,female");
migrationBuilder.CreateTable(
name: "Clients",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "text", nullable: false),
SexType = table.Column<SexType>(type: "public.sex_type", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Clients", x => x.Id);
});
}
在干净的复制项目中使用时,它可以完美运行。它也适用于测试。但是相同的代码(复制粘贴)在给我的主项目中不起作用:
System.InvalidOperationException: An error occurred while reading a database value for property 'Client.SexType'. The expected type was 'MedService.API.Database.Entities.Enums.SexType' but the actual value was of type 'MedService.API.Database.Entities.Enums.SexType'.
---> System.InvalidCastException: Can't cast database type public.sex_type to Int32
at Npgsql.Internal.TypeHandling.NpgsqlTypeHandler.ReadCustom[TAny](NpgsqlReadBuffer buf, Int32 len, Boolean async, FieldDescription fieldDescription)
at Npgsql.NpgsqlDataReader.GetFieldValue[T](Int32 ordinal)
at Npgsql.NpgsqlDataReader.GetInt32(Int32 ordinal)
at lambda_method256(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
--- End of inner exception stack trace ---
at lambda_method256(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
我检查了所有相关的代码,但唯一的区别是数据库上下文的创建方式。主项目使用 DI 的方式与测试项目相同,而 repro 直接创建上下文。所以从逻辑上讲,如果主项目和测试项目工作相同,它们的行为应该相同,但它们不是。
我检查了每个生成的 SQL 脚本、LINQ 表达式等。一切似乎都一样,但我一直收到此异常。
我还应该在哪里寻找问题?
我已确认 sex_type
已在主项目和复制项目中创建。它们是相同的(使用 DBeaver 检查)。不仅如此,数据库结构也完全相同。数据库本身是使用 dotnet ef database update
.
P.S。
不,ReloadTypes()
在这里也无济于事。已经检查过了。
使用的提供商版本:6.0.3
使用的 PostgreSQL 版本:14.2
UPDATE_01
做了一些实验,这个有效:
var connection = (Npgsql.NpgsqlConnection)_medServiceDbContext.Database.GetDbConnection();
connection.Open();
using (var cmd = new Npgsql.NpgsqlCommand("SELECT c.\"Id\", c.\"Name\", c.\"SexType\" FROM public.\"Clients\" as c", connection))
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetFieldValue<int>(0);
var surname = reader.GetFieldValue<string>(1);
var sex = reader.GetFieldValue<SexType>(2);
Console.WriteLine("{0}, {1}, {2}", id, surname, sex);
}
}
而这不是:
_medServiceDbContext.Clients.AsNoTracking().ToArray()
后者给出了上述异常 (Can't cast database type public.sex_type to Int32
)。
我的猜测是出于某种原因 EF 提供程序使用 GetInt32()
override of the NpgsqlDataReader
implementation instead of GetFieldValue<T>()
.
UPDATE_02
实际上我终于 assemble repro. It seems the issue is related to a usage of multiple different contexts targeting the same database connection. Described my findings here。