Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<PackageVersion Include="Dapper" Version="2.1.66" />
<PackageVersion Include="Dapper.AOT" Version="1.0.31" />
<PackageVersion Include="Dapper.StrongName" Version="2.1.66" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.1" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.7.115" />
<PackageVersion Include="Npgsql" Version="9.0.2" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.1" />
Expand Down
9 changes: 8 additions & 1 deletion src/Dapper.AOT/Internal/CommandUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,14 @@ internal static T As<T>(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);
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions test/Dapper.AOT.Test/CommandUtilsEnumTests.cs
Original file line number Diff line number Diff line change
@@ -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<SomeEnum>(value));
}

[Fact]
public void As_StringToEnum()
{
Assert.Equal(SomeEnum.B, CommandUtils.As<SomeEnum>("B"));
}
}
}
15 changes: 12 additions & 3 deletions test/Dapper.AOT.Test/Dapper.AOT.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net48;net9.0</TargetFrameworks>
<NoWarn>$(NoWarn);IDE0042;CS8002;CA1816</NoWarn>
<RootNamespace>Dapper.AOT.Test</RootNamespace>
<DefineConstants>$(DefineConstants);DAPPERAOT_INTERNAL</DefineConstants>
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Dapper.AOT</InterceptorsPreviewNamespaces>
<Deterministic>true</Deterministic>
<DeterministicSourcePaths>true</DeterministicSourcePaths>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Accessors/Data/**/*.*.cs" />
<None Include="Accessors/Data/**/*.*" />
<None Update="Accessors/Data/**/*.*" CopyToOutputDirectory="PreserveNewest" />

<Compile Remove="Interceptors/**/*.*.cs" />
<None Include="Interceptors/**/*.*" />
<None Update="Interceptors/**/*.*" CopyToOutputDirectory="PreserveNewest" />

<Compile Include="..\..\src\Dapper.AOT.Analyzers\AotGridReader.cs" Link="AotGridReader.cs" />
<Compile Include="..\..\src\Dapper.AOT.Analyzers\InGeneration\DapperHelpers.cs" Link="DapperHelpers.cs" />

<None Remove="Interceptors\SqliteUsage.input.cs" />
<None Remove="Interceptors\SqliteUsage.snapshot.cs" />
<Compile Include="Interceptors\SqliteUsage.input.cs" />
<Compile Include="Interceptors\SqliteUsage.snapshot.cs">
<DependentUpon>Interceptors\SqliteUsage.input.cs</DependentUpon>
</Compile>

<None Update="**/*.output.*">
<DependentUpon>$([System.String]::Copy(%(Filename)).Replace('.output', '.input.cs'))</DependentUpon>
</None>
Expand All @@ -26,11 +35,11 @@
<None Update="**/*DAP*.*.cs*">
<DependentUpon>$([System.String]::Copy(%(Filename)).Replace('.VB.cs', '.cs'))</DependentUpon>
</None>

</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper" Condition="'$(TargetFramework)'!='net48'" />
<PackageReference Include="Dapper.StrongName" Condition="'$(TargetFramework)'=='net48'" />
<PackageReference Include="Microsoft.Data.Sqlite" Condition="'$(TargetFramework)'!='net48'" />
<PackageReference Include="Microsoft.Build" Condition="'$(TargetFramework)'!='net8.0'" />
<PackageReference Include="Microsoft.Build" VersionOverride="17.11.4" Condition="'$(TargetFramework)'=='net8.0'" />
<PackageReference Include="Microsoft.Build.Utilities.Core" />
Expand Down
35 changes: 35 additions & 0 deletions test/Dapper.AOT.Test/Integration/SqliteTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#if !NETFRAMEWORK
using SqliteConnection = Microsoft.Data.Sqlite.SqliteConnection;
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);
}
}
}
#endif
27 changes: 27 additions & 0 deletions test/Dapper.AOT.Test/Interceptors/SqliteUsage.input.cs
Original file line number Diff line number Diff line change
@@ -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<TypeWithSomeEnum> GetAll(DbConnection connection)
=> connection.Query<TypeWithSomeEnum>("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,
}
171 changes: 171 additions & 0 deletions test/Dapper.AOT.Test/Interceptors/SqliteUsage.output.cs
Original file line number Diff line number Diff line change
@@ -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: <anonymous type: string name, SomeEnum value>
// 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<global::Dapper.AOT.Test.Integration.TypeWithSomeEnum> 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<T> : global::Dapper.CommandFactory<T>
{
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<object?> DefaultCommandFactory = new();

private sealed class RowFactory0 : global::Dapper.RowFactory<global::Dapper.AOT.Test.Integration.TypeWithSomeEnum>
{
internal static readonly RowFactory0 Instance = new();
private RowFactory0() {}
public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span<int> 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<int> 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<int>(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<string>(reader, columnOffset);
break;
case 2:
result.Value = reader.GetFieldValue<global::Dapper.AOT.Test.Integration.SomeEnum>(columnOffset);
break;
case 5:
result.Value = GetValue<global::Dapper.AOT.Test.Integration.SomeEnum>(reader, columnOffset);
break;

}
columnOffset++;

}
return result;

}

}

private sealed class CommandFactory0 : CommonCommandFactory<object?> // <anonymous type: string name, SomeEnum value>
{
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;
}
}
}
Loading