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

Enum Invalid cast from 'System.Int64' (works without AOT) #92

Open
shiomax opened this issue Dec 1, 2023 · 3 comments
Open

Enum Invalid cast from 'System.Int64' (works without AOT) #92

shiomax opened this issue Dec 1, 2023 · 3 comments
Labels
bug Something isn't working

Comments

@shiomax
Copy link

shiomax commented Dec 1, 2023

The following create statement with an Enum in the class will fail with Invalid cast from 'System.Int64'.

I´m using sqlite, the query that fails looks like this

public async Task<long> CreateAsync(AppUser newUser)
{
    await using var connection = new SqliteConnection(settings.ConnectionString);

    const string sql =
        $"""
          INSERT INTO AppUser
          (Username, Password, UserRole) VALUES
          (
            @{nameof(AppUser.Username)},
            @{nameof(AppUser.Password)},
            @{nameof(AppUser.UserRole)}
          ) RETURNING *;
          """;
        
    var insertedRows = await connection.QueryAsync<AppUser>(sql, newUser);

    return insertedRows.FirstOrDefault()?.Id ?? -1;
}

The Enum is mapped to an INTEGER column in Sqlite. Without AOT the mapping works just fine.

Adding a TypeHandler like this does not seem to work either

internal class UserRoleConverter : SqlMapper.TypeHandler<UserRole>
{
    public override UserRole Parse(object value)
    {
        // theoretically this should fix it as casting to enums only works if it´s int32 afaik, but it never gets called
        var int32Value = Convert.ToInt32(value);
        
        return (UserRole) int32Value;
    }

    public override void SetValue(IDbDataParameter parameter, UserRole value)
    {
        parameter.Value = (int)value;
    }
}

// Registered on startup
SqlMapper.AddTypeHandler(new UserRoleConverter());

I am aware that this query in particular parsing the enum would be avoidable entierly, because I only need the Id back, but this would just be a fix for this one query and it would still be an issue for virtual everything else. It just happens to be the first thing that runs in virtually every single test I have.


There is this issue for regular dapper DapperLib/Dapper#259

So, does that mean if dapper AOT does not handle enums out of the box it just won´t be possible to make it work? I could probably work around this with a class that imitates enums and some explicit converters, but I would rather not.


Tried to create a simple wrapper class like this

public class UserRoleColumn(UserRole userRole)
{
    public UserRole Value { get; set; } = userRole;
}

with this type converter

public class UserRoleColumnConverter : SqlMapper.TypeHandler<UserRoleColumn>
{
    public override void SetValue(IDbDataParameter parameter, UserRoleColumn? value)
    {
        ArgumentNullException.ThrowIfNull(value);

        parameter.Value = (int) value.Value;
    }

    public override UserRoleColumn? Parse(object value)
    {
        var userRole = (UserRole) Convert.ToInt32(value);
        return new UserRoleColumn(userRole);
    }
}

Also does not work throws No mapping exists from object type UserRoleColumn to a known managed provider native type in the same place.


On further inspection SqlMapper.AddTypeHandler is not yet AOT friendly. It breaks on publish so wether it´s used or not by the code generated does not even matter because you can´t register them.

@mgravell mgravell added the bug Something isn't working label Dec 13, 2023
@mgravell
Copy link
Member

Definitely needs fixing, thanks - will get it done

mgravell added a commit that referenced this issue Dec 18, 2023
in advance of changes for #92
@sergiojrdotnet
Copy link

Invalid cast exception also happens when mapping from string

System.InvalidCastException: Invalid cast from 'System.String' to 'Server.Api.Gender'.
   at System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)
   at Dapper.RowFactory.GetValue[T](DbDataReader reader, Int32 fieldOffset) in /_/src/Dapper.AOT/RowFactory.cs:line 63
   at Dapper.AOT.<Server_Api_generated>F47B2F87C501D4F35221EF03A78C5DBAABCE6EC43E2564390DB7B2AE9F7DF49FC__DapperGeneratedInterceptors.RowFactory0.Read(DbDataReader reader, ReadOnlySpan`1 tokens, Int32 columnOffset, Object state) in C:\Users\sergi\OneDrive\Repositories\e-Pigeon\src\Server.Api\Dapper.AOT.Analyzers\Dapper.CodeAnalysis.DapperInterceptorGenerator\Server.Api.generated.cs:line 257
   at Dapper.RowFactory`1.Read(DbDataReader reader, Int32[]& lease) in /_/src/Dapper.AOT/RowFactory.cs:line 199
   at Dapper.Command`1.QueryOneRowAsync[TRow](TArgs args, OneRowFlags flags, RowFactory`1 rowFactory, CancellationToken cancellationToken)
   at Dapper.Command`1.QueryOneRowAsync[TRow](TArgs args, OneRowFlags flags, RowFactory`1 rowFactory, CancellationToken cancellationToken) in /_/src/Dapper.AOT/CommandT.Query.cs:line 249
   at Server.Api.Repositories.AccountsRepository.GetAsync(Guid id, CancellationToken cancellationToken) in C:\Users\sergi\OneDrive\Repositories\e-Pigeon\src\Server.Api\Repositories\AccountsRepository.cs:line 44
   at Server.Api.Repositories.AccountsRepository.GetAsync(Guid id, CancellationToken cancellationToken) in C:\Users\sergi\OneDrive\Repositories\e-Pigeon\src\Server.Api\Repositories\AccountsRepository.cs:line 44
   at Microsoft.AspNetCore.Http.Generated.<GeneratedRouteBuilderExtensions_g>F5A64FF4688F51B3A1261E1448D208859037CDC6F99DBFF1E67ED92F9177C1895__GeneratedRouteBuilderExtensionsCore.<>c__DisplayClass4_0.<<MapGet0>g__RequestHandler|4>d.MoveNext() in C:\Users\sergi\OneDrive\Repositories\e-Pigeon\src\Server.Api\Microsoft.AspNetCore.Http.RequestDelegateGenerator\Microsoft.AspNetCore.Http.RequestDelegateGenerator.RequestDelegateGenerator\GeneratedRouteBuilderExtensions.g.cs:line 168
--- End of stack trace from previous location ---
   at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

@Dean-NC
Copy link

Dean-NC commented Mar 21, 2024

This also happens when querying a column in SQL Server defined as tinyint or smallint and attempting to map to enum, whereas non-AOT works.
Example for tinyint: InvalidCastException from 'System.Byte' to enum

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants