.NET 9与PostgreSQL 9.0.1升级后EF Core无法持久化HashSet<T>
问题描述
将项目从.NET 6升级至.NET 9,同时把PostgreSQL升级到9.0.1版本。领域模型中有如下属性:
public HashSet<Guid> Property { get; set; }
数据库中该属性存储为jsonb列,默认值设为'[]'::jsonb。升级后出现以下错误:
System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.HashSet`1[System.Guid]' to type 'System.Collections.Generic.IList`1[System.Guid]'.
推测问题与升级后EF Core对HashSet<T>和jsonb列的序列化/反序列化机制有关,此前在.NET 6及旧版PostgreSQL环境中功能正常。
尝试手动配置序列化但未解决问题:
builder .Property(x => x.Property) .HasColumnType("jsonb") .HasDefaultValueSql("'[]'::jsonb") .HasConversion( v => JsonSerializer.Serialize(v, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }), v => JsonSerializer.Deserialize<HashSet<Guid>>(v, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new HashSet<Guid>() );
咨询以下问题:
- EF Core或PostgreSQL是否存在影响
HashSet<T>与jsonb列序列化/反序列化的破坏性变更? - 如何在升级后的环境中正确持久化和查询
HashSet<T>与jsonb列? - 有无规避该
InvalidCastException的解决方案或最佳实践?
解决方案与说明
1. 破坏性变更说明
- EF Core 8+(含.NET 9对应的版本)对集合类型的JSON序列化逻辑做了调整:默认会将JSON数组反序列化为
List<T>而非HashSet<T>,当模型属性定义为HashSet<T>时,内部尝试将List<T>强制转换为HashSet<T>就会抛出InvalidCastException。 - Npgsql(PostgreSQL的EF Core驱动)在新版本中同步了这一行为,不再自动将JSON数组映射为
HashSet<T>,优先使用List<T>作为默认集合类型。
2. 正确持久化与查询配置
有两种可靠的配置方式:
方式一:自定义值转换器并指定集合类型
修改值转换器,明确指定反序列化生成HashSet<T>,同时添加值比较器让EF Core识别属性类型:
builder.Property(x => x.Property) .HasColumnType("jsonb") .HasDefaultValueSql("'[]'::jsonb") .HasConversion( v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default), v => JsonSerializer.Deserialize<HashSet<Guid>>(v, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new HashSet<Guid>(), new ValueConverterMappingHints { ValueComparer = ValueComparer.CreateDefault<HashSet<Guid>>() } );
方式二:Npgsql驱动专属配置
如果使用Npgsql驱动,可直接指定JSON列映射为HashSet<T>:
// 单个属性配置 builder.Property(x => x.Property) .HasColumnType("jsonb") .HasDefaultValueSql("'[]'::jsonb") .HasConversion<HashSet<Guid>>();
若需全局配置,可在DbContext的OnConfiguring中设置:
optionsBuilder.UseNpgsql(connectionString, o => o.UseSystemTextJson().ConfigureJsonOptions(opt => { // 可添加其他JSON配置,比如枚举转换器 opt.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); }) );
3. 规避InvalidCastException的解决方案
临时快速修复
在模型属性的setter中主动转换集合类型,避免强制转换错误:
private HashSet<Guid> _property = new(); public HashSet<Guid> Property { get => _property; set => _property = value != null ? new HashSet<Guid>(value) : new HashSet<Guid>(); }
最佳实践
- 优先使用自定义值转换器配置,明确指定集合类型的序列化/反序列化逻辑,不依赖默认行为。
- 升级前核对EF Core和Npgsql的版本变更日志,重点关注集合类型、JSON处理相关的破坏性变更。
- 确保模型属性初始化时设置为
new HashSet<Guid>(),与数据库默认值'[]'::jsonb匹配,避免空引用问题。
内容的提问来源于stack exchange,提问作者coder123
相关产品推荐
相关产品推荐

