DapperLib / DapperLib/Dapper

ITypeHandler is not used when creating parameters from list

Open
#1,965 0 comments 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
18.4k
Forks
3.7k
Avg merge
5h 8m
Merged PRs (30d)
1

Description

Hi,

I'm working with Dapper and Oracle database and using Guid data type on some of my columns. Due to that fact I had to create custom TypeHandler in order to properly convert .Net Guid to Oracle Guid string representation.

class CustomGuidTypeHandler : SqlMapper.TypeHandler<Guid>
{
    public override void SetValue(IDbDataParameter parameter, Guid value)
    {
        parameter.Value = value.FlipEndian().ToString("N").ToUpper();
    }

    public override Guid Parse(object value)
    {
        return new Guid((byte[])value).FlipEndian();
    }
}

FlipEndian implementation from:
https://stackoverflow.com/questions/8064648/net-native-guid-conversion?lq=1

Everything works just fine for simple queries like these:

partial class Repository
{
    private readonly OracleConnection _oracleConnection;

    public Repository(OracleConnection oracleConnection)
    {
        _oracleConnection = oracleConnection;
    }

    public Task<Guid> GetFirstId()
    {
        return _oracleConnection.QueryFirstAsync<Guid>("SELECT ID FROM SOME_TABLE FETCH FIRST 1 ROWS ONLY");
    }

    public Task<bool> DoesExist(Guid id)
    {
        return _oracleConnection.ExecuteScalarAsync<bool>("SELECT COUNT(1) FROM SOME_TABLE WHERE ID = :id", new { id }
    }
}

Problem is when we try to use Guid inside collection, exception is gonna be thrown.

partial class Repository
{
    public Task<IEnumerable<Guid>> GetExisting(IEnumerable<Guid> ids)
    {
        return _oracleConnection.QueryAsync<Guid>("SELECT ID FROM SOME_TABLE WHERE ID IN :ids", new { ids });
    }
}

When I manually map Guid to string everything works just fine.

partial class Repository
{
    public Task<IEnumerable<Guid>> GetExistingMapGuidToStringManually(IEnumerable<Guid> ids)
    {
        return _oracleConnection.QueryAsync<Guid>(
            "SELECT ID FROM SOME_TABLE WHERE ID IN :ids",
            new
            {
                ids = ids.Select(x => x.FlipEndian().ToString("N").ToUpper()),
            }
        );
    }
}

I was debugging this and I think I have found the issue.
When it comes to the single Guid parameters in the CreateParamInfoGenerator method the LookupDbType method is used to check if there is a custom handler for parameter type, and later on TypeHandlerCache is called to convert type.

internal static Action<IDbCommand, object?> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList<LiteralToken> literals)
{
    // [...]
    DbType? dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler? handler);
    // [...]
    il.Emit(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(prop.PropertyType).GetMethod(nameof(TypeHandlerCache<int>.SetValue))!); // stack is now [parameters] [[parameters]] [parameter]
    // [...]
}

But for the collection Guid parameter flow is different. The PackListParameters method is used, and there there is a call to LookupDbType in order to check if custom handler for parameter type exists, but later on handler is not used.

public static void PackListParameters(IDbCommand command, string namePrefix, object? value)
{
    // [...]
    dbType = LookupDbType(item.GetType(), "", true, out var handler);
    // [...]
    var tmp = listParam.Value = SanitizeParameterValue(item);
    // [...]

So this line: https://github.com/DapperLib/Dapper/blob/main/Dapper/SqlMapper.cs#L2187C29-L2187C86 throws an exception.

IMO this line:

var tmp = listParam.Value = SanitizeParameterValue(item);

needs to be replaced with something like this (use ITypeHandler)

var tmp = listParam.Value = handler is not null 
    ? handler.SetValue(listParam, item)
    : SanitizeParameterValue(item);

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in Dapper/SqlMapper.cs at PackListParameters and compare its type-handler lookup with CreateParamInfoGenerator. Verify how the handler is expected to set each list parameter, then confirm that a registered Guid handler is applied for collection values and that the Oracle-style query no longer throws.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, sql
Domain
database
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.