Skip to content

Add TypeHandlerFactory for open-generic type handler registration#2211

Open
PauloHMattos wants to merge 1 commit into
DapperLib:mainfrom
PauloHMattos:feature/type-handler-factory
Open

Add TypeHandlerFactory for open-generic type handler registration#2211
PauloHMattos wants to merge 1 commit into
DapperLib:mainfrom
PauloHMattos:feature/type-handler-factory

Conversation

@PauloHMattos
Copy link
Copy Markdown

Dapper's AddTypeHandler API requires a concrete closed type and a concrete handler instance. There was no way to register a single handler that covers all closed variants of an open generic type (e.g., HashSet<T>, List<T>) without registering each element type individually.

Proposed solution

Introduces SqlMapper.TypeHandlerFactory, an abstract class modelled after System.Text.Json.Serialization.JsonConverterFactory, that lets users register a single factory covering an entire family of types.

public abstract class TypeHandlerFactory
{
    public abstract bool CanHandle(Type type);
    public abstract ITypeHandler Create(Type type);
}

Register a factory with the new AddTypeHandlerFactory method:

public class MyListHandlerFactory : SqlMapper.TypeHandlerFactory
{
    public override bool CanHandle(Type type)
        => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>);

    public override ITypeHandler Create(Type type)
    {
        var elementType = type.GetGenericArguments()[0];
        return (SqlMapper.ITypeHandler)Activator.CreateInstance(
            typeof(MyListHandler<>).MakeGenericType(elementType))!;
    }
}

SqlMapper.AddTypeHandlerFactory(new MyListHandlerFactory());

Implementation details

  • When Dapper needs a handler for a type and finds no direct entry in typeHandlers, it queries each registered factory in registration order. The first factory whose CanHandle returns true wins.
  • On a factory hit, Create is called and the resulting handler is immediately registered via AddTypeHandlerCore, which updates both the typeHandlers dictionary and TypeHandlerCache<T> (used by IL emitted code paths). Subsequent lookups for the same type are O(1) dictionary reads so the factory is only consulted once per type.
  • HasTypeHandler also checks factories, so callers can introspect before executing queries.
  • ResetTypeHandlers() clears all registered factories.
  • Thread safety follows the existing snapshot/mutate/swap pattern used for typeHandlers.

New API surface

public static class SqlMapper
{
+    public static void AddTypeHandlerFactory(SqlMapper.TypeHandlerFactory factory);

+    public abstract class TypeHandlerFactory 
+    {
+        public abstract bool CanHandle(System.Type type);
+        public abstract ITypeHandler Create(System.Type type);
+    }
}

Closes #2190.

@PauloHMattos PauloHMattos force-pushed the feature/type-handler-factory branch from 97f5549 to 02bede9 Compare May 20, 2026 12:28
@PauloHMattos PauloHMattos force-pushed the feature/type-handler-factory branch from 02bede9 to b828bb7 Compare May 20, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Support for Open Generic TypeHandler registration

1 participant