Steps to reproduce
public static class UnitTests
{
private static string _connectionString =
"Server=localhost;Port=5432;Database=myDatabase;User Id=myUserName;Password=myPassword";
public static void FailsWithConnectionSpecificMappingAndDapper()
{
using (NpgsqlConnection connection = new NpgsqlConnection(_connectionString))
{
connection.Open();
connection.TypeMapper.RemoveMapping("text");
connection.TypeMapper.AddMapping(new NpgsqlTypeMappingBuilder
{
PgTypeName = "citext",
NpgsqlDbType = NpgsqlDbType.Citext,
DbTypes = new[] { DbType.String },
ClrTypes = new[] { typeof(string) },
TypeHandlerFactory = new TextHandlerFactory()
}.Build());
if (!connection.Query<bool>(
"SELECT @p = 'hello'::citext", new { p = "HeLLo" }).First()) // fails here
{
throw new Exception("failed"); // never makes it here
}
}
}
public static void WorksWithGlobalMappingAndDapper()
{
NpgsqlConnection.GlobalTypeMapper.RemoveMapping("text");
NpgsqlConnection.GlobalTypeMapper.AddMapping(new NpgsqlTypeMappingBuilder
{
PgTypeName = "citext",
NpgsqlDbType = NpgsqlDbType.Citext,
DbTypes = new[] { DbType.String },
ClrTypes = new[] { typeof(string) },
TypeHandlerFactory = new TextHandlerFactory()
}.Build());
using (NpgsqlConnection connection = new NpgsqlConnection(_connectionString))
{
connection.Open();
if (!connection.Query<bool>(
"SELECT @p = 'hello'::citext", new { p = "HeLLo" }).First())
{
throw new Exception("failed");
}
}
}
public static void WorksWithConnectionSpecificMappingAndAdoNet()
{
using (NpgsqlConnection connection = new NpgsqlConnection(_connectionString))
{
connection.Open();
connection.TypeMapper.RemoveMapping("text");
connection.TypeMapper.AddMapping(new NpgsqlTypeMappingBuilder
{
PgTypeName = "citext",
NpgsqlDbType = NpgsqlDbType.Citext,
DbTypes = new[] { DbType.String },
ClrTypes = new[] { typeof(string) },
TypeHandlerFactory = new TextHandlerFactory()
}.Build());
using (NpgsqlCommand command = new NpgsqlCommand(
"SELECT @p = 'hello'::citext", connection))
{
command.Parameters.AddWithValue("p", "HeLLo");
if (!(bool)command.ExecuteScalar())
{
throw new Exception("failed");
}
}
}
}
}
Note: Make sure you run FailsWithConnectionSpecificMappingAndDapper 1st because if WorksWithGlobalMappingAndDapper runs 1st it will mask the bug.
The issue
I'm using the citext mapping that you suggested in response to #1765 (related to #1475).
I think there is a bug, but it's a rare use case so it probably hasn't been noticed.
Your unit test here shows how to map it at the connection-specific level. This works fine with ADO.Net, but fails with Dapper. I've debugged it a bit & believe it's an Npgsql issue, not a Dapper issue. However, NpgsqlConnection.GlobalTypeMapper works with both Ado.Net & Dapper.
From stepping through the code it seems to be related to GlobalTypeMapper.Instance.ToNpgsqlDbType(value) not getting setup correctly when the mapping is changed on a local connection. I haven't debugged any more than that.
On a side note, PostgreSQL can implicitly type-cast between text & citext if needed, so it may not be obvious that you need to remap citext in npgsql because your queries might return the correct results even without the mapping. The reason I even attempted to map citext was because my queries were extremely slow in npgsql but fast in pgAdmin. After lengthy debugging, I realized it was because my table columns were citext, but I wasn't mapping to citext in npgsql. Maybe this could be mentioned at https://www.npgsql.org/doc/performance.html to help other people avoid my mistake?
Exception Message:
{Npgsql.NpgsqlException (0x80004005): The NpgsqlDbType 'Text' isn't present in your database. You may need to install an extension or upgrade to a newer version.
Stack Trace:
at Npgsql.TypeMapping.ConnectorTypeMapper.GetByNpgsqlDbType(NpgsqlDbType npgsqlDbType) in C:\projects\npgsql\src\Npgsql\TypeMapping\ConnectorTypeMapper.cs:line 106
at Npgsql.NpgsqlParameter.ResolveHandler(ConnectorTypeMapper typeMapper) in C:\projects\npgsql\src\Npgsql\NpgsqlParameter.cs:line 523
at Npgsql.NpgsqlCommand.ValidateParameters() in C:\projects\npgsql\src\Npgsql\NpgsqlCommand.cs:line 796
at Npgsql.NpgsqlCommand.ExecuteDbDataReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken) in C:\projects\npgsql\src\Npgsql\NpgsqlCommand.cs:line 1141
at Npgsql.NpgsqlCommand.ExecuteDbDataReader(CommandBehavior behavior) in C:\projects\npgsql\src\Npgsql\NpgsqlCommand.cs:line 1130
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader(CommandBehavior behavior)
at Dapper.SqlMapper.ExecuteReaderWithFlagsFallback(IDbCommand cmd, Boolean wasClosed, CommandBehavior behavior) in C:\projects\dapper\Dapper\SqlMapper.cs:line 1053
at Dapper.SqlMapper.QueryImpl[T](IDbConnection cnn, CommandDefinition command, Type effectiveType)+MoveNext() in C:\projects\dapper\Dapper\SqlMapper.cs:line 1081
at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Dapper.SqlMapper.Query[T](IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in C:\projects\dapper\Dapper\SqlMapper.cs:line 723
Further technical details
Npgsql version: 4.0.3
Dapper: 1.50.5
PostgreSQL version: PostgreSQL 10.1, compiled by Visual C++ build 1800, 64-bit
Operating system: Windows 10 Pro 64-bot
Steps to reproduce
Note: Make sure you run
FailsWithConnectionSpecificMappingAndDapper1st because ifWorksWithGlobalMappingAndDapperruns 1st it will mask the bug.The issue
I'm using the
citextmapping that you suggested in response to #1765 (related to #1475).I think there is a bug, but it's a rare use case so it probably hasn't been noticed.
Your unit test here shows how to map it at the connection-specific level. This works fine with ADO.Net, but fails with Dapper. I've debugged it a bit & believe it's an Npgsql issue, not a Dapper issue. However,
NpgsqlConnection.GlobalTypeMapperworks with both Ado.Net & Dapper.From stepping through the code it seems to be related to
GlobalTypeMapper.Instance.ToNpgsqlDbType(value)not getting setup correctly when the mapping is changed on a local connection. I haven't debugged any more than that.On a side note, PostgreSQL can implicitly type-cast between
text&citextif needed, so it may not be obvious that you need to remapcitextin npgsql because your queries might return the correct results even without the mapping. The reason I even attempted to mapcitextwas because my queries were extremely slow in npgsql but fast in pgAdmin. After lengthy debugging, I realized it was because my table columns werecitext, but I wasn't mapping tocitextin npgsql. Maybe this could be mentioned at https://www.npgsql.org/doc/performance.html to help other people avoid my mistake?Exception Message:
Stack Trace:
Further technical details
Npgsql version: 4.0.3
Dapper: 1.50.5
PostgreSQL version: PostgreSQL 10.1, compiled by Visual C++ build 1800, 64-bit
Operating system: Windows 10 Pro 64-bot