Skip to content

connection.TypeMapper.RemoveMapping / AddMapping bug #2177

@speige

Description

@speige

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions