Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type handler with generic type #1924

Open
regazzoj opened this issue Jun 20, 2023 · 6 comments
Open

Type handler with generic type #1924

regazzoj opened this issue Jun 20, 2023 · 6 comments

Comments

@regazzoj
Copy link

Hello,
For now, we can add type handler for class without generic type.

In our software, we need possibility to add a type handler for a generic type like this one =>
public class Ref<T> {...}

We think that there could be a delegate function which check if the given object can be assignable to the generic type when we add a type handler =>
SqlMapper.AddTypeHandler(t => t.IsAssignableTo(typeof(Ref)), new RefTypeHandler());

And to finish, the type handler methods should have the wanted type in parameter, so we can parse and set correctly =>

public sealed class RefTypeHandler : SqlMapper.TypeHandler<Ref>
{
    public override void SetValue(IDbDataParameter parameter, Ref value, Type type)
    {
        throw new System.NotImplementedException();
    }

    public override Ref Parse(object value, Type type)
    {
        throw new System.NotImplementedException();
    }
}

This is not an issue, but a new functionality, but I don't know where to write it, and it might be not relevant as well.
Just let me know !

Regards !

@mgravell
Copy link
Member

Yes, the type-handler API is limited and applied inconsistently; if we make a change here, it will be over in the AOT project, but what I'm thinking is something along the lines of:

/// <summary>
/// Specify that <typeparamref name="TTypeHandler"/> should be used as a custom type-handler
/// when processing values of type <typeparamref name="TValue"/>
/// </summary>
[ImmutableObject(true)]
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public sealed class TypeHandlerAttribute<TValue, TTypeHandler> : Attribute
    where TTypeHandler : TypeHandler<TValue>, new()
{}

/// <summary>
/// Process a parameter value of type <typeparamref name="T"/>
/// </summary>
public abstract class TypeHandler<T>
{
    /// <summary>
    /// Configure and assign a value for a parameter
    /// </summary>
    public virtual void SetValue(DbParameter parameter, T value)
        => parameter.Value = CommandFactory.AsGenericValue(value); // default handling, already exists

    /// <summary>
    /// Interpret the output value from a parameter
    /// </summary>
    public virtual T Parse(DbParameter parameter)
        => CommandUtils.As<T>(parameter.Value); // default handling, already exists
}

with example usage:

[module: TypeHandler<SomeType, SomeTypeHandler>]
[module: TypeHandler<SomeRef<object>, SomeRefHandler<object>>] // object here is placeholder only

class SomeTypeHandler : TypeHandler<SomeType> { }
struct SomeType { }

class SomeRef<T> { }
class SomeRefHandler<T> : TypeHandler<SomeRef<T>> { }

which would allow SomeRefHandler<T> to override Parse and/or SetValue, and do whatever it needs; this would be after the default config (DbType, Size, etc), and would be instead of the default .Value = ...

Would this meet your idea?

mgravell added a commit to DapperLib/DapperAOT that referenced this issue Jun 20, 2023
@agjini
Copy link

agjini commented Jun 21, 2023

Hello Marc,

thanks for your quick answer.
I'm working with @regazzoj, so I'm aware of what he ask.

We are not sure to understand your answer (a little confusing for us).

I will try to explain you the problem we are facing with the actual version of DapperLib :

As mentioned in the issue earlier we have a generic Ref<T>.
This type inherits from Ref abstract class.

public abstract class Ref{}

public class Ref<T> : Ref {}

Today with Dapper (not AOT) we can easily define a TypeHandler like this one :

// Version 1
public class RefTypeHandler<T> : SqlMapper.TypeHandler<Ref<T>>
{
    public override void SetValue(IDbDataParameter parameter, Ref<T> value)
    {
        throw new System.NotImplementedException();
    }

    public override Ref<T> Parse(object value)
    {
        throw new System.NotImplementedException();
    }
}

or this one

// Version 2
public class RefTypeHandler : SqlMapper.TypeHandler<Ref>
{
    public override void SetValue(IDbDataParameter parameter, Ref value)
    {
        throw new System.NotImplementedException();
    }

    public override Ref Parse(object value)
    {
        throw new System.NotImplementedException();
    }
}

The problem is in the 2 cases we cannot register the typehandler in Dapper to handler all generic Ref types.

// Version 1
SqlMapper.AddTypeHandler(new RefTypeHandler());
// only registered for Ref abstract type, does not match any Ref<T> subtypes at runtime.
// Version 2
SqlMapper.AddTypeHandler(typeof(Ref<>), typeof(RefTypeHandler<>));
// does not compile

The only solution is to manually register each concrete type as this :

// Version 3
SqlMapper.AddTypeHandler(new RefTypeHandler<Referential>()); // register Ref<Referential>
SqlMapper.AddTypeHandler(new RefTypeHandler<UserGroup>()); // register Ref<UserGroup>
// ... but the list can grow

maybe the problem is clearer for you now ?

@mgravell
Copy link
Member

It was never not clear. My point is: yes, we know it is limited right now, and my plan is to focus all feature additions at the AOT model, which is many times easier (and more efficient) to maintain, so I was trying to be clear "if we make a change to this, it is likely to be in the AOT timescale and require the AOT lib"

@regazzoj
Copy link
Author

Great ! Thanks for your answer !
The syntax used in your previous answer was confusing for us.
Too much things we don't know about...
Thanks for your time ! Can't wait to see AOT lib then 😄

@justinmchase
Copy link

Why not just use [TypeConverter] attributes that may be on the target type? You probably have common mappings for types already, for example you can already map a SqlString to a string, so if the type has a [TypeConverter(typeof(ExampleConverter))] which points to a converter type of ValueConverter<Example, string>.

Then you'd use the converter to convert to and from a string and your built-in mapper to map to known types. Is that not reasonable? Many types are already have these converters.

@KieranDevvs
Copy link

KieranDevvs commented Jun 5, 2024

Why not just use [TypeConverter] attributes that may be on the target type? You probably have common mappings for types already, for example you can already map a SqlString to a string, so if the type has a [TypeConverter(typeof(ExampleConverter))] which points to a converter type of ValueConverter<Example, string>.

Then you'd use the converter to convert to and from a string and your built-in mapper to map to known types. Is that not reasonable? Many types are already have these converters.

I have a scenario where I need to type convert date times to UTC (by specifying the kind from Unknown to UTC) but I dont want to map all DateTimes to UTC. Right now, its all or nothing and I have no way of saying only apply the mapping to this property. Attributes are really needed for scenarios like these.

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

No branches or pull requests

5 participants