From 1160de913b34cbeabdd0dad7ca171d1ebe5204ba Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 18 Dec 2023 15:02:24 +0000 Subject: [PATCH 1/5] repro for sqlite enum handling in advance of changes for #92 --- Directory.Packages.props | 78 ++++---- test/Dapper.AOT.Test/Dapper.AOT.Test.csproj | 20 +- .../Integration/SqliteTests.cs | 38 ++++ .../Interceptors/SqliteUsage.input.cs | 27 +++ .../Interceptors/SqliteUsage.output.cs | 171 ++++++++++++++++++ .../Interceptors/SqliteUsage.output.netfx.cs | 171 ++++++++++++++++++ .../Interceptors/SqliteUsage.output.netfx.txt | 12 ++ .../Interceptors/SqliteUsage.output.txt | 12 ++ .../Interceptors/SqliteUsage.snapshot.cs | 171 ++++++++++++++++++ 9 files changed, 658 insertions(+), 42 deletions(-) create mode 100644 test/Dapper.AOT.Test/Integration/SqliteTests.cs create mode 100644 test/Dapper.AOT.Test/Interceptors/SqliteUsage.input.cs create mode 100644 test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.cs create mode 100644 test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.netfx.cs create mode 100644 test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.netfx.txt create mode 100644 test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.txt create mode 100644 test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index e7f3774c..a7086a03 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,40 +1,42 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj index 10da715a..851b33df 100644 --- a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj +++ b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj @@ -4,6 +4,7 @@ $(NoWarn);IDE0042;CS8002;CA1816 Dapper.AOT.Test $(DefineConstants);DAPPERAOT_INTERNAL + $(InterceptorsPreviewNamespaces);Dapper.AOT @@ -12,7 +13,16 @@ + + + + + + + + Interceptors\SqliteUsage.input.cs + @@ -21,14 +31,16 @@ $([System.String]::Copy(%(Filename)).Replace('.output.netfx', '.input.cs')) - - $([System.String]::Copy(%(Filename)).Replace('.VB.cs', '.cs')) - + + $([System.String]::Copy(%(Filename)).Replace('.VB.cs', '.cs')) + - + + + diff --git a/test/Dapper.AOT.Test/Integration/SqliteTests.cs b/test/Dapper.AOT.Test/Integration/SqliteTests.cs new file mode 100644 index 00000000..b5743617 --- /dev/null +++ b/test/Dapper.AOT.Test/Integration/SqliteTests.cs @@ -0,0 +1,38 @@ +#if NETFRAMEWORK +using SqliteConnection = System.Data.SQLite.SQLiteConnection; +#else +using SqliteConnection = Microsoft.Data.Sqlite.SqliteConnection; +#endif + +using System; +using Xunit; + +namespace Dapper.AOT.Test.Integration +{ + public sealed class SqliteTests : IDisposable + { + private readonly SqliteConnection connection; + + public void Dispose() => connection.Dispose(); + + [DapperAot(false)] + public SqliteTests() + { + connection = new("Data Source=:memory:"); + connection.Open(); + connection.Execute("CREATE TABLE Foo(Id INTEGER PRIMARY KEY ASC, Name TEXT, Value INTEGER)"); // vanilla Dapper + } + + [Fact] + public void InsertAndSelect() + { + // use the generated output from SqliteUsage.Output.cs + foreach (SomeEnum e in Enum.GetValues(typeof(SomeEnum))) + { + SqliteUsage.Insert(connection, e.ToString(), e); + } + + var rows = SqliteUsage.GetAll(connection); + } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/SqliteUsage.input.cs b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.input.cs new file mode 100644 index 00000000..104439b2 --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.input.cs @@ -0,0 +1,27 @@ +using Dapper; +using System.Collections.Generic; +using System.Data.Common; + +namespace Dapper.AOT.Test.Integration; + +[DapperAot] +static class SqliteUsage +{ + public static void Insert(DbConnection connection, string name, SomeEnum value) + => connection.Execute("INSERT INTO Foo(Name, Value) VALUES ($name, $value)", new { name, value }); + + public static List GetAll(DbConnection connection) + => connection.Query("SELECT Id, Name, Value FROM Foo").AsList(); +} + +public class TypeWithSomeEnum +{ + public int Id { get; set; } + public string Name { get; set; } + public SomeEnum Value { get; set; } +} + +public enum SomeEnum +{ + A = 1, B = 2, C = 3, +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.cs b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.cs new file mode 100644 index 00000000..6d007dba --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.cs @@ -0,0 +1,171 @@ +#nullable enable +namespace Dapper.AOT // interceptors must be in a known namespace +{ + file static class DapperGeneratedInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\SqliteUsage.input.cs", 11, 23)] + internal static int Execute0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Execute, HasParameters, Text, KnownParameters + // takes parameter: + // parameter map: name value + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(param is not null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory0.Instance).Execute(param); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\SqliteUsage.input.cs", 14, 23)] + internal static global::System.Collections.Generic.IEnumerable Query1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, Text, BindResultsByName + // returns data: global::Dapper.AOT.Test.Integration.TypeWithSomeEnum + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory0.Instance); + + } + + private class CommonCommandFactory : global::Dapper.CommandFactory + { + public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection, string sql, global::System.Data.CommandType commandType, T args) + { + var cmd = base.GetCommand(connection, sql, commandType, args); + // apply special per-provider command initialization logic for OracleCommand + if (cmd is global::Oracle.ManagedDataAccess.Client.OracleCommand cmd0) + { + cmd0.BindByName = true; + cmd0.InitialLONGFetchSize = -1; + + } + return cmd; + } + + } + + private static readonly CommonCommandFactory DefaultCommandFactory = new(); + + private sealed class RowFactory0 : global::Dapper.RowFactory + { + internal static readonly RowFactory0 Instance = new(); + private RowFactory0() {} + public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) + { + for (int i = 0; i < tokens.Length; i++) + { + int token = -1; + var name = reader.GetName(columnOffset); + var type = reader.GetFieldType(columnOffset); + switch (NormalizedHash(name)) + { + case 926444256U when NormalizedEquals(name, "id"): + token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible + break; + case 2369371622U when NormalizedEquals(name, "name"): + token = type == typeof(string) ? 1 : 4; + break; + case 1113510858U when NormalizedEquals(name, "value"): + token = type == typeof(global::Dapper.AOT.Test.Integration.SomeEnum) ? 2 : 5; + break; + + } + tokens[i] = token; + columnOffset++; + + } + return null; + } + public override global::Dapper.AOT.Test.Integration.TypeWithSomeEnum Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) + { + global::Dapper.AOT.Test.Integration.TypeWithSomeEnum result = new(); + foreach (var token in tokens) + { + switch (token) + { + case 0: + result.Id = reader.GetInt32(columnOffset); + break; + case 3: + result.Id = GetValue(reader, columnOffset); + break; + case 1: + result.Name = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); + break; + case 4: + result.Name = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); + break; + case 2: + result.Value = reader.GetFieldValue(columnOffset); + break; + case 5: + result.Value = GetValue(reader, columnOffset); + break; + + } + columnOffset++; + + } + return result; + + } + + } + + private sealed class CommandFactory0 : CommonCommandFactory // + { + internal static readonly CommandFactory0 Instance = new(); + public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { name = default(string)!, value = default(global::Dapper.AOT.Test.Integration.SomeEnum) }); // expected shape + var ps = cmd.Parameters; + global::System.Data.Common.DbParameter p; + p = cmd.CreateParameter(); + p.ParameterName = "name"; + p.DbType = global::System.Data.DbType.String; + p.Direction = global::System.Data.ParameterDirection.Input; + SetValueWithDefaultSize(p, typed.name); + ps.Add(p); + + p = cmd.CreateParameter(); + p.ParameterName = "value"; + p.Direction = global::System.Data.ParameterDirection.Input; + p.Value = AsValue(typed.value); + ps.Add(p); + + } + public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { name = default(string)!, value = default(global::Dapper.AOT.Test.Integration.SomeEnum) }); // expected shape + var ps = cmd.Parameters; + ps[0].Value = AsValue(typed.name); + ps[1].Value = AsValue(typed.value); + + } + + } + + + } +} +namespace System.Runtime.CompilerServices +{ + // this type is needed by the compiler to implement interceptors - it doesn't need to + // come from the runtime itself, though + + [global::System.Diagnostics.Conditional("DEBUG")] // not needed post-build, so: evaporate + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + sealed file class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) + { + _ = path; + _ = lineNumber; + _ = columnNumber; + } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.netfx.cs new file mode 100644 index 00000000..6d007dba --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.netfx.cs @@ -0,0 +1,171 @@ +#nullable enable +namespace Dapper.AOT // interceptors must be in a known namespace +{ + file static class DapperGeneratedInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\SqliteUsage.input.cs", 11, 23)] + internal static int Execute0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Execute, HasParameters, Text, KnownParameters + // takes parameter: + // parameter map: name value + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(param is not null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory0.Instance).Execute(param); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\SqliteUsage.input.cs", 14, 23)] + internal static global::System.Collections.Generic.IEnumerable Query1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, Text, BindResultsByName + // returns data: global::Dapper.AOT.Test.Integration.TypeWithSomeEnum + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory0.Instance); + + } + + private class CommonCommandFactory : global::Dapper.CommandFactory + { + public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection, string sql, global::System.Data.CommandType commandType, T args) + { + var cmd = base.GetCommand(connection, sql, commandType, args); + // apply special per-provider command initialization logic for OracleCommand + if (cmd is global::Oracle.ManagedDataAccess.Client.OracleCommand cmd0) + { + cmd0.BindByName = true; + cmd0.InitialLONGFetchSize = -1; + + } + return cmd; + } + + } + + private static readonly CommonCommandFactory DefaultCommandFactory = new(); + + private sealed class RowFactory0 : global::Dapper.RowFactory + { + internal static readonly RowFactory0 Instance = new(); + private RowFactory0() {} + public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) + { + for (int i = 0; i < tokens.Length; i++) + { + int token = -1; + var name = reader.GetName(columnOffset); + var type = reader.GetFieldType(columnOffset); + switch (NormalizedHash(name)) + { + case 926444256U when NormalizedEquals(name, "id"): + token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible + break; + case 2369371622U when NormalizedEquals(name, "name"): + token = type == typeof(string) ? 1 : 4; + break; + case 1113510858U when NormalizedEquals(name, "value"): + token = type == typeof(global::Dapper.AOT.Test.Integration.SomeEnum) ? 2 : 5; + break; + + } + tokens[i] = token; + columnOffset++; + + } + return null; + } + public override global::Dapper.AOT.Test.Integration.TypeWithSomeEnum Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) + { + global::Dapper.AOT.Test.Integration.TypeWithSomeEnum result = new(); + foreach (var token in tokens) + { + switch (token) + { + case 0: + result.Id = reader.GetInt32(columnOffset); + break; + case 3: + result.Id = GetValue(reader, columnOffset); + break; + case 1: + result.Name = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); + break; + case 4: + result.Name = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); + break; + case 2: + result.Value = reader.GetFieldValue(columnOffset); + break; + case 5: + result.Value = GetValue(reader, columnOffset); + break; + + } + columnOffset++; + + } + return result; + + } + + } + + private sealed class CommandFactory0 : CommonCommandFactory // + { + internal static readonly CommandFactory0 Instance = new(); + public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { name = default(string)!, value = default(global::Dapper.AOT.Test.Integration.SomeEnum) }); // expected shape + var ps = cmd.Parameters; + global::System.Data.Common.DbParameter p; + p = cmd.CreateParameter(); + p.ParameterName = "name"; + p.DbType = global::System.Data.DbType.String; + p.Direction = global::System.Data.ParameterDirection.Input; + SetValueWithDefaultSize(p, typed.name); + ps.Add(p); + + p = cmd.CreateParameter(); + p.ParameterName = "value"; + p.Direction = global::System.Data.ParameterDirection.Input; + p.Value = AsValue(typed.value); + ps.Add(p); + + } + public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { name = default(string)!, value = default(global::Dapper.AOT.Test.Integration.SomeEnum) }); // expected shape + var ps = cmd.Parameters; + ps[0].Value = AsValue(typed.name); + ps[1].Value = AsValue(typed.value); + + } + + } + + + } +} +namespace System.Runtime.CompilerServices +{ + // this type is needed by the compiler to implement interceptors - it doesn't need to + // come from the runtime itself, though + + [global::System.Diagnostics.Conditional("DEBUG")] // not needed post-build, so: evaporate + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + sealed file class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) + { + _ = path; + _ = lineNumber; + _ = columnNumber; + } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.netfx.txt b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.netfx.txt new file mode 100644 index 00000000..47c3de59 --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.netfx.txt @@ -0,0 +1,12 @@ +Input code has 1 diagnostics from 'Interceptors/SqliteUsage.input.cs': + +Hidden CS8019 Interceptors/SqliteUsage.input.cs L1 C1 +Unnecessary using directive. +Generator produced 1 diagnostics: + +Hidden DAP000 L1 C1 +Dapper.AOT handled 2 of 2 possible call-sites using 2 interceptors, 1 commands and 1 readers +Output code has 1 diagnostics from 'Interceptors/SqliteUsage.input.cs': + +Hidden CS8019 Interceptors/SqliteUsage.input.cs L1 C1 +Unnecessary using directive. diff --git a/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.txt b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.txt new file mode 100644 index 00000000..47c3de59 --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.txt @@ -0,0 +1,12 @@ +Input code has 1 diagnostics from 'Interceptors/SqliteUsage.input.cs': + +Hidden CS8019 Interceptors/SqliteUsage.input.cs L1 C1 +Unnecessary using directive. +Generator produced 1 diagnostics: + +Hidden DAP000 L1 C1 +Dapper.AOT handled 2 of 2 possible call-sites using 2 interceptors, 1 commands and 1 readers +Output code has 1 diagnostics from 'Interceptors/SqliteUsage.input.cs': + +Hidden CS8019 Interceptors/SqliteUsage.input.cs L1 C1 +Unnecessary using directive. diff --git a/test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs new file mode 100644 index 00000000..1bf66610 --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs @@ -0,0 +1,171 @@ +#nullable enable +namespace Dapper.AOT // interceptors must be in a known namespace +{ + file static class DapperGeneratedInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(@"C:\Code\DapperAOT\test\Dapper.AOT.Test\Interceptors\SqliteUsage.input.cs", 11, 23)] + internal static int Execute0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Execute, HasParameters, Text, KnownParameters + // takes parameter: + // parameter map: name value + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(param is not null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory0.Instance).Execute(param); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(@"C:\Code\DapperAOT\test\Dapper.AOT.Test\Interceptors\SqliteUsage.input.cs", 14, 23)] + internal static global::System.Collections.Generic.IEnumerable Query1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, Text, BindResultsByName + // returns data: global::Dapper.AOT.Test.Integration.TypeWithSomeEnum + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, RowFactory0.Instance); + + } + + private class CommonCommandFactory : global::Dapper.CommandFactory + { + public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection, string sql, global::System.Data.CommandType commandType, T args) + { + var cmd = base.GetCommand(connection, sql, commandType, args); + // apply special per-provider command initialization logic for OracleCommand + if (cmd is global::Oracle.ManagedDataAccess.Client.OracleCommand cmd0) + { + cmd0.BindByName = true; + cmd0.InitialLONGFetchSize = -1; + + } + return cmd; + } + + } + + private static readonly CommonCommandFactory DefaultCommandFactory = new(); + + private sealed class RowFactory0 : global::Dapper.RowFactory + { + internal static readonly RowFactory0 Instance = new(); + private RowFactory0() {} + public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) + { + for (int i = 0; i < tokens.Length; i++) + { + int token = -1; + var name = reader.GetName(columnOffset); + var type = reader.GetFieldType(columnOffset); + switch (NormalizedHash(name)) + { + case 926444256U when NormalizedEquals(name, "id"): + token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible + break; + case 2369371622U when NormalizedEquals(name, "name"): + token = type == typeof(string) ? 1 : 4; + break; + case 1113510858U when NormalizedEquals(name, "value"): + token = type == typeof(global::Dapper.AOT.Test.Integration.SomeEnum) ? 2 : 5; + break; + + } + tokens[i] = token; + columnOffset++; + + } + return null; + } + public override global::Dapper.AOT.Test.Integration.TypeWithSomeEnum Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) + { + global::Dapper.AOT.Test.Integration.TypeWithSomeEnum result = new(); + foreach (var token in tokens) + { + switch (token) + { + case 0: + result.Id = reader.GetInt32(columnOffset); + break; + case 3: + result.Id = GetValue(reader, columnOffset); + break; + case 1: + result.Name = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); + break; + case 4: + result.Name = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); + break; + case 2: + result.Value = reader.GetFieldValue(columnOffset); + break; + case 5: + result.Value = GetValue(reader, columnOffset); + break; + + } + columnOffset++; + + } + return result; + + } + + } + + private sealed class CommandFactory0 : CommonCommandFactory // + { + internal static readonly CommandFactory0 Instance = new(); + public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { name = default(string)!, value = default(global::Dapper.AOT.Test.Integration.SomeEnum) }); // expected shape + var ps = cmd.Parameters; + global::System.Data.Common.DbParameter p; + p = cmd.CreateParameter(); + p.ParameterName = "name"; + p.DbType = global::System.Data.DbType.String; + p.Direction = global::System.Data.ParameterDirection.Input; + SetValueWithDefaultSize(p, typed.name); + ps.Add(p); + + p = cmd.CreateParameter(); + p.ParameterName = "value"; + p.Direction = global::System.Data.ParameterDirection.Input; + p.Value = AsValue(typed.value); + ps.Add(p); + + } + public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { name = default(string)!, value = default(global::Dapper.AOT.Test.Integration.SomeEnum) }); // expected shape + var ps = cmd.Parameters; + ps[0].Value = AsValue(typed.name); + ps[1].Value = AsValue(typed.value); + + } + + } + + + } +} +namespace System.Runtime.CompilerServices +{ + // this type is needed by the compiler to implement interceptors - it doesn't need to + // come from the runtime itself, though + + [global::System.Diagnostics.Conditional("DEBUG")] // not needed post-build, so: evaporate + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + sealed file class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) + { + _ = path; + _ = lineNumber; + _ = columnNumber; + } + } +} \ No newline at end of file From f6d3343f5c5669b4f8b248f34e0f1770f96a57db Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 18 Dec 2023 16:58:40 +0000 Subject: [PATCH 2/5] use mapped path rather than absolute --- test/Dapper.AOT.Test/Dapper.AOT.Test.csproj | 7 +------ test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj index 851b33df..8f37d874 100644 --- a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj +++ b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj @@ -5,26 +5,22 @@ Dapper.AOT.Test $(DefineConstants);DAPPERAOT_INTERNAL $(InterceptorsPreviewNamespaces);Dapper.AOT + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))=./ - - - - Interceptors\SqliteUsage.input.cs - $([System.String]::Copy(%(Filename)).Replace('.output', '.input.cs')) @@ -34,7 +30,6 @@ $([System.String]::Copy(%(Filename)).Replace('.VB.cs', '.cs')) - diff --git a/test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs index 1bf66610..4666ef35 100644 --- a/test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs +++ b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs @@ -3,7 +3,7 @@ namespace Dapper.AOT // interceptors must be in a known namespace { file static class DapperGeneratedInterceptors { - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(@"C:\Code\DapperAOT\test\Dapper.AOT.Test\Interceptors\SqliteUsage.input.cs", 11, 23)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(@"./Interceptors/SqliteUsage.input.cs", 11, 23)] internal static int Execute0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Execute, HasParameters, Text, KnownParameters @@ -17,7 +17,7 @@ internal static int Execute0(this global::System.Data.IDbConnection cnn, string } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(@"C:\Code\DapperAOT\test\Dapper.AOT.Test\Interceptors\SqliteUsage.input.cs", 14, 23)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(@"./Interceptors/SqliteUsage.input.cs", 14, 23)] internal static global::System.Collections.Generic.IEnumerable Query1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, TypedResult, Buffered, Text, BindResultsByName From f9eec49c357dbf51815c16bc564b32d41c098b50 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 18 Dec 2023 17:03:06 +0000 Subject: [PATCH 3/5] use inbuilt deterministic mode --- test/Dapper.AOT.Test/Dapper.AOT.Test.csproj | 3 ++- test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj index 8f37d874..b28ea748 100644 --- a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj +++ b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj @@ -5,7 +5,8 @@ Dapper.AOT.Test $(DefineConstants);DAPPERAOT_INTERNAL $(InterceptorsPreviewNamespaces);Dapper.AOT - $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))=./ + true + true diff --git a/test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs index 4666ef35..28fe1c9f 100644 --- a/test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs +++ b/test/Dapper.AOT.Test/Interceptors/SqliteUsage.snapshot.cs @@ -3,7 +3,7 @@ namespace Dapper.AOT // interceptors must be in a known namespace { file static class DapperGeneratedInterceptors { - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(@"./Interceptors/SqliteUsage.input.cs", 11, 23)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(@"/_/test/Dapper.AOT.Test/Interceptors/SqliteUsage.input.cs", 11, 23)] internal static int Execute0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Execute, HasParameters, Text, KnownParameters @@ -17,7 +17,7 @@ internal static int Execute0(this global::System.Data.IDbConnection cnn, string } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(@"./Interceptors/SqliteUsage.input.cs", 14, 23)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(@"/_/test/Dapper.AOT.Test/Interceptors/SqliteUsage.input.cs", 14, 23)] internal static global::System.Collections.Generic.IEnumerable Query1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, TypedResult, Buffered, Text, BindResultsByName From b6ecb3ff6a6fcaaa29ad052678c50103052ff35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Bl=C3=BCher?= Date: Sat, 21 Mar 2026 11:35:23 +0100 Subject: [PATCH 4/5] fix enum mapping in As fallback path (#92) Convert.ChangeType does not support enum target types, causing InvalidCastException when the reader returns Int64/Byte/String for enum-typed properties. Use Enum.ToObject for numeric values and Enum.Parse for strings instead. Also adjust SqliteTests to exclude NETFRAMEWORK since main dropped the System.Data.SQLite package reference. --- src/Dapper.AOT/Internal/CommandUtils.cs | 9 ++++++++- test/Dapper.AOT.Test/Integration/SqliteTests.cs | 9 +++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Dapper.AOT/Internal/CommandUtils.cs b/src/Dapper.AOT/Internal/CommandUtils.cs index 00ac9414..997274b7 100644 --- a/src/Dapper.AOT/Internal/CommandUtils.cs +++ b/src/Dapper.AOT/Internal/CommandUtils.cs @@ -303,7 +303,14 @@ internal static T As(object? value) } else { - return (T)Convert.ChangeType(value, Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T), CultureInfo.InvariantCulture); + var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); + if (targetType.IsEnum) + { + return (s = value as string) is not null + ? (T)Enum.Parse(targetType, s, true) + : (T)Enum.ToObject(targetType, value); + } + return (T)Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture); } } } diff --git a/test/Dapper.AOT.Test/Integration/SqliteTests.cs b/test/Dapper.AOT.Test/Integration/SqliteTests.cs index b5743617..10875307 100644 --- a/test/Dapper.AOT.Test/Integration/SqliteTests.cs +++ b/test/Dapper.AOT.Test/Integration/SqliteTests.cs @@ -1,9 +1,5 @@ -#if NETFRAMEWORK -using SqliteConnection = System.Data.SQLite.SQLiteConnection; -#else +#if !NETFRAMEWORK using SqliteConnection = Microsoft.Data.Sqlite.SqliteConnection; -#endif - using System; using Xunit; @@ -35,4 +31,5 @@ public void InsertAndSelect() var rows = SqliteUsage.GetAll(connection); } } -} \ No newline at end of file +} +#endif \ No newline at end of file From 39dac59ddd8ea1affab46b6658346b4061d471e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Bl=C3=BCher?= Date: Sat, 21 Mar 2026 11:39:54 +0100 Subject: [PATCH 5/5] add unit tests for enum handling in As Cover Int64, Int32, Byte to enum, string to enum (case-insensitive), nullable enum, and null-to-nullable-enum paths. --- test/Dapper.AOT.Test/CommandUtilsEnumTests.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test/Dapper.AOT.Test/CommandUtilsEnumTests.cs diff --git a/test/Dapper.AOT.Test/CommandUtilsEnumTests.cs b/test/Dapper.AOT.Test/CommandUtilsEnumTests.cs new file mode 100644 index 00000000..c013c7e6 --- /dev/null +++ b/test/Dapper.AOT.Test/CommandUtilsEnumTests.cs @@ -0,0 +1,23 @@ +using Dapper.AOT.Test.Integration; +using Dapper.Internal; +using Xunit; + +namespace Dapper.AOT.Test +{ + public class CommandUtilsEnumTests + { + [Theory] + [InlineData(1L, SomeEnum.A)] // Int64 (SQLite) + [InlineData(2, SomeEnum.B)] // Int32 (SQL Server) + public void As_NumericToEnum(object value, SomeEnum expected) + { + Assert.Equal(expected, CommandUtils.As(value)); + } + + [Fact] + public void As_StringToEnum() + { + Assert.Equal(SomeEnum.B, CommandUtils.As("B")); + } + } +}