Npgsql 8.0搭配Dapper处理Protobuf重复字段请求时失效
问题场景
在.NET 8环境下,原使用Npgsql 7.x搭配Dapper的代码可正常运行,但升级至Npgsql 8.0后,执行查询时抛出System.InvalidCastException异常。
相关代码
public class Location { public NpgsqlPoint Point { get; set; } } public async Task<IEnumerable<Location>> GetLocationsAsync(DomainParams paramObject, CancellationToken cancellationToken) { using (NpgsqlConnection conn = new(_connectionString)) { await conn.OpenAsync(cancellationToken); return await conn.QueryAsync<Location>("select point from locationsTable where id = ANY(@ids)", new { paramObject.Ids } ); } }
异常详情
"Depth": 0,
"HelpURL": null,
"HResult": -2147467262,
"ClassName": "System.InvalidCastException",
"RemoteStackIndex": 0,
"RemoteStackTraceString": null,
"StackTraceString": " at Npgsql.Internal.AdoSerializerHelpers.g__ThrowWritingNotSupported|1_0(Type type, PgSerializerOptions options, Nullable1 pgTypeId, Nullable1 npgsqlDbType, Exception inner)\n at Npgsql.Internal.AdoSerializerHelpers.GetTypeInfoForWriting(Type type, Nullable1 pgTypeId, PgSerializerOptions options, Nullable1 npgsqlDbType)\n at Npgsql.NpgsqlParameter.ResolveTypeInfo(PgSerializerOptions options)\n at Npgsql.NpgsqlParameterCollection.ProcessParameters(PgSerializerOptions options, Boolean validateValues, CommandType commandType)\n at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)\n at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)\n at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)\n at Dapper.SqlMapper.QueryAsync[T](IDbConnection cnn, Type effectiveType, CommandDefinition command) in /_/Dapper/SqlMapper.Async.cs:line 434"
原因分析
Npgsql 8.0对参数序列化系统进行了重构,默认逻辑与7.x版本存在差异。Dapper自动生成的数组参数(@ids)未被正确识别为PostgreSQL数组类型,导致序列化阶段触发类型转换失败。
解决方案
1. 显式指定参数类型
手动创建参数并明确指定PostgreSQL数组类型,绕过Dapper自动处理的问题:
public async Task<IEnumerable<Location>> GetLocationsAsync(DomainParams paramObject, CancellationToken cancellationToken) { using (NpgsqlConnection conn = new(_connectionString)) { await conn.OpenAsync(cancellationToken); var parameters = new DynamicParameters(); // 根据实际id的数据类型调整NpgsqlDbType,示例为整型数组 parameters.Add("@ids", paramObject.Ids, NpgsqlDbType.Array | NpgsqlDbType.Integer); return await conn.QueryAsync<Location>( "select point from locationsTable where id = ANY(@ids)", parameters, cancellationToken: cancellationToken ); } }
2. 回退到旧版参数处理逻辑
若暂时不想修改代码,可在连接字符串中添加配置项,启用Legacy兼容模式:
Server=xxx;Port=5432;Database=xxx;User Id=xxx;Password=xxx;UseLegacyTimestampBehavior=true;UseLegacyAdoNetBehavior=true
注意:该配置为过渡方案,未来版本可能移除,建议优先采用第一种方案。
3. 更新Dapper至最新版本
确保使用的Dapper为最新稳定版,新版本已针对Npgsql 8.0做适配,可能直接解决参数识别问题。
内容的提问来源于stack exchange,提问作者DanM

