DapperLib / DapperLib/Dapper

No Way to Pass Enumerated Types to Underlying Implementation

Open
#332 14 comments 8 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

With the latest release of Npgsql (v3), enumerations should be passed as the enumerated type (not the underlying integral type). Unfortunately, the only way to do this using Dapper (v1.42) is by wrapping every enumeration parameter in an SqlMapper.ICustomQueryParameter class (cannot add a type handler due to issues #259 and #286). Even if those issues are resolved, it would be nice if there was some setting that enabled Dapper to bypass the if (type.IsEnum() && !typeMap.ContainsKey(type)) portion of SqlMapper.LookupDbType, as well as the if (value is Enum) portion of SqlMapper.SanitizeParameterValue. Then, passing enumerated values as parameters would work with Npgsql without issue. For example, here is the existing code for LookupDbType and SanitizeParameterValue in SqlMapper.cs:

internal static DbType LookupDbType(Type type,
    string name, bool demand, out ITypeHandler handler)
{
    DbType dbType;
    handler = null;
    var nullUnderlyingType = Nullable.GetUnderlyingType(type);
    if (nullUnderlyingType != null) type = nullUnderlyingType;
    if (type.IsEnum() && !typeMap.ContainsKey(type))
    {
        type = Enum.GetUnderlyingType(type);
    }
    if (typeMap.TryGetValue(type, out dbType))
    {
        return dbType;
    }
    if (type.FullName == LinqBinary)
    {
        return DbType.Binary;
    }
    if (typeHandlers.TryGetValue(type, out handler))
    {
        return DbType.Object;
    }
    if (typeof(IEnumerable).IsAssignableFrom(type))
    {
        return DynamicParameters.EnumerableMultiParameter;
    }

#if !DNXCORE50
    switch (type.FullName)
    {
        case "Microsoft.SqlServer.Types.SqlGeography":
            AddTypeHandler(type, handler = new UdtTypeHandler("geography"));
            return DbType.Object;
        case "Microsoft.SqlServer.Types.SqlGeometry":
            AddTypeHandler(type, handler = new UdtTypeHandler("geometry"));
            return DbType.Object;
        case "Microsoft.SqlServer.Types.SqlHierarchyId":
            AddTypeHandler(type, handler = new UdtTypeHandler("hierarchyid"));
            return DbType.Object;
    }
#endif
    if (demand)
        throw new NotSupportedException(string.Format(
            "The member {0} of type {1} cannot be used as a parameter value",
            name, type.FullName));
    return DbType.Object;
}




internal static object SanitizeParameterValue(object value)
{
    if (value == null) return DBNull.Value;
    if (value is Enum)
    {
        TypeCode typeCode;
        if (value is IConvertible)
        {
            typeCode = ((IConvertible)value).GetTypeCode();
        }
        else
        {
            typeCode = TypeExtensions.GetTypeCode(Enum.GetUnderlyingType(value.GetType()));
        }
        switch (typeCode)
        {
            case TypeCode.Byte: return (byte)value;
            case TypeCode.SByte: return (sbyte)value;
            case TypeCode.Int16: return (short)value;
            case TypeCode.Int32: return (int)value;
            case TypeCode.Int64: return (long)value;
            case TypeCode.UInt16: return (ushort)value;
            case TypeCode.UInt32: return (uint)value;
            case TypeCode.UInt64: return (ulong)value;
        }
    }
    return value;
}

My proposal is to add the following to SqlMapper.cs:

// The default is to convert enumerated values to the underlying type,
//     to prevent breaking existing code
public static bool SanitizeEnumValues = true;

And to modify the following conditional in SqlMapper.LookupDbType:

// Existing conditional
if (type.IsEnum() && !typeMap.ContainsKey(type))
// Proposed change
if (SanitizeEnumValues && type.IsEnum() && !typeMap.ContainsKey(type))

And to modify the following conditional in SqlMapper.SanitizeParameterValue:

// Existing conditional
if (value is Enum)
// Proposed change
if (SanitizeEnumValues && value is Enum)

I think such a change is of minimal risk, since the default behavior is to do exactly what Dapper does now. However, this would enable me to gracefully work with Npgsql enumeration support.

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 SqlMapper.cs with LookupDbType and SanitizeParameterValue, using the proposed SanitizeEnumValues setting and the existing enum branches as the scope. Verify that the default preserves current enum conversion while disabling it permits enumerated parameters for Npgsql; completion should cover both code paths and their compatibility behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend, databases
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.