diff --git a/Directory.Packages.props b/Directory.Packages.props
index 37851bc2..6e583e05 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -33,6 +33,7 @@
+
diff --git a/src/Dapper.AOT/Internal/CommandUtils.cs b/src/Dapper.AOT/Internal/CommandUtils.cs
index a03a9fa1..00ac9414 100644
--- a/src/Dapper.AOT/Internal/CommandUtils.cs
+++ b/src/Dapper.AOT/Internal/CommandUtils.cs
@@ -165,6 +165,46 @@ internal static T As(object? value)
DateTime? t = Convert.ToDateTime(value, CultureInfo.InvariantCulture);
return Unsafe.As(ref t);
}
+#if NET6_0_OR_GREATER
+ else if (typeof(T) == typeof(DateOnly))
+ {
+ if (value is DateOnly only) return Unsafe.As(ref only);
+
+ DateTime t = Convert.ToDateTime(value, CultureInfo.InvariantCulture);
+ var dateOnly = DateOnly.FromDateTime(t);
+ return Unsafe.As(ref dateOnly);
+ }
+ else if (typeof(T) == typeof(DateOnly?))
+ {
+ DateTime? t = Convert.ToDateTime(value, CultureInfo.InvariantCulture);
+ DateOnly? dateOnly = t is null ? null : DateOnly.FromDateTime(t.Value);
+ return Unsafe.As(ref dateOnly);
+ }
+ else if (typeof(T) == typeof(TimeOnly))
+ {
+ if (value is TimeSpan timeSpan)
+ {
+ var fromSpan = TimeOnly.FromTimeSpan(timeSpan);
+ return Unsafe.As(ref fromSpan);
+ }
+
+ DateTime t = Convert.ToDateTime(value, CultureInfo.InvariantCulture);
+ var timeOnly = TimeOnly.FromDateTime(t);
+ return Unsafe.As(ref timeOnly);
+ }
+ else if (typeof(T) == typeof(TimeOnly?))
+ {
+ if (value is TimeSpan timeSpan)
+ {
+ var fromSpan = TimeOnly.FromTimeSpan(timeSpan);
+ return Unsafe.As(ref fromSpan);
+ }
+
+ DateTime? t = Convert.ToDateTime(value, CultureInfo.InvariantCulture);
+ TimeOnly? timeOnly = t is null ? null : TimeOnly.FromDateTime(t.Value);
+ return Unsafe.As(ref timeOnly);
+ }
+#endif
else if (typeof(T) == typeof(Guid) && (s = value as string) is not null)
{
Guid t = Guid.Parse(s);
diff --git a/src/Dapper.AOT/Properties/AssemblyInfo.cs b/src/Dapper.AOT/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..62522625
--- /dev/null
+++ b/src/Dapper.AOT/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Dapper.AOT.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a17ba361da0990b3da23f3c20f2a002242397b452a28f27832d61d49f35edb54a68b98d98557b8a02be79be42142339c7861af309c8917dee972775e2c358dd6b96109a9147987652b25b8dc52e7f61f22a755831674f0a3cea17bef9abb6b23ef1856a02216864a1ffbb04a4c549258d32ba740fe141dad2f298a8130ea56d0")]
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration.Executables/Dapper.AOT.Test.Integration.Executables.csproj b/test/Dapper.AOT.Test.Integration.Executables/Dapper.AOT.Test.Integration.Executables.csproj
index 828f4929..b996bea0 100644
--- a/test/Dapper.AOT.Test.Integration.Executables/Dapper.AOT.Test.Integration.Executables.csproj
+++ b/test/Dapper.AOT.Test.Integration.Executables/Dapper.AOT.Test.Integration.Executables.csproj
@@ -1,6 +1,6 @@
- net8.0;net6.0;net48
+ net8.0;net6.0Dapper.AOT.Test.Integration.Executables
diff --git a/test/Dapper.AOT.Test.Integration.Executables/Models/DateOnlyTimeOnlyPoco.cs b/test/Dapper.AOT.Test.Integration.Executables/Models/DateOnlyTimeOnlyPoco.cs
new file mode 100644
index 00000000..a7e3e683
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/Models/DateOnlyTimeOnlyPoco.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Dapper.AOT.Test.Integration.Executables.Models;
+
+public class DateOnlyTimeOnlyPoco
+{
+ public const string TableName = "dateOnlyTimeOnly";
+
+ public static DateOnly SpecificDate => DateOnly.FromDateTime(new DateTime(year: 2022, month: 2, day: 2));
+ public static TimeOnly SpecificTime => TimeOnly.FromDateTime(new DateTime(year: 2022, month: 1, day: 1, hour: 10, minute: 11, second: 12));
+
+ public int Id { get; set; }
+ public DateOnly Date { get; set; }
+ public TimeOnly Time { get; set; }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyInsert.cs b/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyInsert.cs
new file mode 100644
index 00000000..6e530980
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyInsert.cs
@@ -0,0 +1,30 @@
+using System.Data;
+using System.Linq;
+using Dapper.AOT.Test.Integration.Executables.Models;
+
+namespace Dapper.AOT.Test.Integration.Executables.UserCode.DateOnlyTimeOnly;
+
+[DapperAot]
+public class DateOnlyInsert : IExecutable
+{
+ public DateOnlyTimeOnlyPoco Execute(IDbConnection connection)
+ {
+ connection.Execute(
+ $"""
+ insert into {DateOnlyTimeOnlyPoco.TableName}(id, date, time)
+ values (@id, @date, @time)
+ on conflict (id) do nothing;
+ """,
+ new DateOnlyTimeOnlyPoco { Id = 2, Date = DateOnlyTimeOnlyPoco.SpecificDate, Time = DateOnlyTimeOnlyPoco.SpecificTime }
+ );
+
+ var results = connection.Query(
+ $"""
+ select * from {DateOnlyTimeOnlyPoco.TableName}
+ where id = 2
+ """
+ );
+
+ return results.First();
+ }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyTimeOnlyUsage.cs b/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyTimeOnlyUsage.cs
new file mode 100644
index 00000000..2ab1685b
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyTimeOnlyUsage.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Data;
+using System.Linq;
+using Dapper.AOT.Test.Integration.Executables.Models;
+
+namespace Dapper.AOT.Test.Integration.Executables.UserCode.DateOnlyTimeOnly;
+
+[DapperAot]
+public class DateOnlyTimeOnlyUsage : IExecutable
+{
+ public DateOnlyTimeOnlyPoco Execute(IDbConnection connection)
+ {
+ var results = connection.Query($"select * from {DateOnlyTimeOnlyPoco.TableName}");
+ return results.First();
+ }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyUsageWithDateFilter.cs b/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyUsageWithDateFilter.cs
new file mode 100644
index 00000000..036096ba
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyUsageWithDateFilter.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Data;
+using System.Linq;
+using Dapper.AOT.Test.Integration.Executables.Models;
+
+namespace Dapper.AOT.Test.Integration.Executables.UserCode.DateOnlyTimeOnly;
+
+[DapperAot]
+public class DateOnlyUsageWithDateFilter : IExecutable
+{
+ public DateOnlyTimeOnlyPoco Execute(IDbConnection connection)
+ {
+ var results = connection.Query(
+ $"""
+ select * from {DateOnlyTimeOnlyPoco.TableName}
+ where date = @date
+ """,
+ new { date = DateOnlyTimeOnlyPoco.SpecificDate }
+ );
+
+ return results.First();
+ }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyUsageWithTimeFilter.cs b/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyUsageWithTimeFilter.cs
new file mode 100644
index 00000000..ffdf5f26
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration.Executables/UserCode/DateOnlyTimeOnly/DateOnlyUsageWithTimeFilter.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Data;
+using System.Linq;
+using Dapper.AOT.Test.Integration.Executables.Models;
+
+namespace Dapper.AOT.Test.Integration.Executables.UserCode.DateOnlyTimeOnly;
+
+[DapperAot]
+public class DateOnlyUsageWithTimeFilter : IExecutable
+{
+ public DateOnlyTimeOnlyPoco Execute(IDbConnection connection)
+ {
+ var results = connection.Query(
+ $"""
+ select * from {DateOnlyTimeOnlyPoco.TableName}
+ where time = @time
+ """,
+ new { time = DateOnlyTimeOnlyPoco.SpecificTime }
+ );
+
+ return results.First();
+ }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration/DateOnlyTimeOnlyTests.cs b/test/Dapper.AOT.Test.Integration/DateOnlyTimeOnlyTests.cs
new file mode 100644
index 00000000..ba48aa73
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration/DateOnlyTimeOnlyTests.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Data;
+using Dapper.AOT.Test.Integration.Executables.Models;
+using Dapper.AOT.Test.Integration.Executables.UserCode.DateOnlyTimeOnly;
+using Dapper.AOT.Test.Integration.Setup;
+
+namespace Dapper.AOT.Test.Integration;
+
+[Collection(SharedPostgresqlClient.Collection)]
+public class DateOnlyTimeOnlyTests : IntegrationTestsBase
+{
+ public DateOnlyTimeOnlyTests(PostgresqlFixture fixture) : base(fixture)
+ {
+ }
+
+ protected override void SetupDatabase(IDbConnection dbConnection)
+ {
+ base.SetupDatabase(dbConnection);
+
+ dbConnection.Execute($"""
+ CREATE TABLE IF NOT EXISTS {DateOnlyTimeOnlyPoco.TableName}(
+ id integer PRIMARY KEY,
+ date DATE,
+ time TIME
+ );
+
+ TRUNCATE {DateOnlyTimeOnlyPoco.TableName};
+
+ INSERT INTO {DateOnlyTimeOnlyPoco.TableName} (id, date, time)
+ VALUES (1, '{DateOnlyTimeOnlyPoco.SpecificDate.ToString("yyyy-MM-dd")}', '{DateOnlyTimeOnlyPoco.SpecificTime.ToString("HH:mm:ss")}')
+ """);
+ }
+
+ [Fact]
+ public void DateOnly_BasicUsage_InterceptsAndReturnsExpectedData()
+ {
+ var result = ExecuteInterceptedUserCode(DbConnection);
+ Assert.Equal(1, result.Id);
+ Assert.Equal(DateOnlyTimeOnlyPoco.SpecificDate, result.Date);
+ Assert.Equal(DateOnlyTimeOnlyPoco.SpecificTime, result.Time);
+ }
+
+ [Fact]
+ public void DateOnly_WithDateFilter_InterceptsAndReturnsExpectedData()
+ {
+ var result = ExecuteInterceptedUserCode(DbConnection);
+ Assert.Equal(1, result.Id);
+ Assert.Equal(DateOnlyTimeOnlyPoco.SpecificDate, result.Date);
+ Assert.Equal(DateOnlyTimeOnlyPoco.SpecificTime, result.Time);
+ }
+
+ [Fact]
+ public void DateOnly_WithTimeFilter_InterceptsAndReturnsExpectedData()
+ {
+ var result = ExecuteInterceptedUserCode(DbConnection);
+ Assert.Equal(1, result.Id);
+ Assert.Equal(DateOnlyTimeOnlyPoco.SpecificDate, result.Date);
+ Assert.Equal(DateOnlyTimeOnlyPoco.SpecificTime, result.Time);
+ }
+
+ [Fact]
+ public void DateOnly_Inserts_InterceptsAndReturnsExpectedData()
+ {
+ var result = ExecuteInterceptedUserCode(DbConnection);
+ Assert.Equal(2, result.Id);
+ Assert.Equal(DateOnlyTimeOnlyPoco.SpecificDate, result.Date);
+ Assert.Equal(DateOnlyTimeOnlyPoco.SpecificTime, result.Time);
+ }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration/README.md b/test/Dapper.AOT.Test.Integration/README.md
new file mode 100644
index 00000000..b3314245
--- /dev/null
+++ b/test/Dapper.AOT.Test.Integration/README.md
@@ -0,0 +1,29 @@
+# Dapper.AOT.Test - Integration
+
+This is a project for integration tests against a non-mocked database using generated interceptor bits.
+
+### Requirements
+Make sure you have Docker Desktop running to be able to initialize a container with PostgreSQL db (or other).
+
+### How to add a new test
+1) Add your tests to `Dapper.AOT.Test.Integration` and inherit the [IntegrationTestBase](./Setup/IntegrationTestsBase.cs) class
+2) Override `SetupDatabase` with in example creating a table and filling it in with some data to query later
+3) Call `IntegrationTestBase.ExecuteInterceptedUserCode()` where:
+ - `TUsage` is a type specified in [Dapper.AOT.Test.Integration.Executables/UserCode](../Dapper.AOT.Test.Integration.Executables/UserCode) project
+ and implements [IExecutable](../Dapper.AOT.Test.Integration.Executables/IExecutable.cs) interface.
+ The method `IExecutable.Execute(DbConnection connection)` will be executed by the framework automatically
+ - `TPoco` is the return type of `IExecutable.Execute(DbConnection connection)`. `TPoco` has to be also defined in [Dapper.AOT.Test.Integration.Executables/Models](../Dapper.AOT.Test.Integration.Executables/Models)
+4) Assert that returned `TPoco` has all the data you expected
+
+### How test framework works
+
+- Create compilation with reference to user code ([Dapper.AOT.Test.Integration.Executables](../Dapper.AOT.Test.Integration.Executables)) via `CSharpCompilation.Create()`
+- give it to `ISourceGenerator` and get an output compilation
+- call `outputCompilation.Emit()` and get an assembly
+- create instance of user code via reflection (Activator) - this is where we need the `IExecutable` interface to properly cast the type to it
+- call `IExecutable.Execute(DbConnection connection)` passing the connection to container-db
+- get the output `object`, cast it back to `TPoco` (also defined in [Dapper.AOT.Test.Integration.Executables](../Dapper.AOT.Test.Integration.Executables)) and return it to the test.
+ Developer can write assertions based on the returned object
+
+_note_: SourceGenerator is specifically created with the injected `IInterceptorRecorder`, which gets the stack trace of execution and verifies that generated bits were executed.
+This gives a full confidence that Interceptors worked as expected. Also it reports diagnostics if something unexpected occured, and test verifies there are none of those.
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test.Integration/Setup/IntegrationTestsBase.cs b/test/Dapper.AOT.Test.Integration/Setup/IntegrationTestsBase.cs
index 762259de..3ea3ec41 100644
--- a/test/Dapper.AOT.Test.Integration/Setup/IntegrationTestsBase.cs
+++ b/test/Dapper.AOT.Test.Integration/Setup/IntegrationTestsBase.cs
@@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
+using Microsoft.Data.SqlClient;
namespace Dapper.AOT.Test.Integration.Setup;
@@ -61,6 +62,7 @@ protected TResult ExecuteInterceptedUserCode(IDbConnection
// Additional stuff required by Dapper.AOT generators
MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location), // System.Collections
+ MetadataReference.CreateFromFile(Assembly.Load("System.Collections.Immutable").Location), // System.Collections.Immutable
],
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
@@ -77,8 +79,8 @@ protected TResult ExecuteInterceptedUserCode(IDbConnection
var mainMethod = type.GetMethod(nameof(IExecutable.Execute), BindingFlags.Public | BindingFlags.Instance);
var result = mainMethod!.Invoke(obj: executableInstance, [ dbConnection ]);
- Assert.True(interceptorRecorder.WasCalled);
- Assert.True(string.IsNullOrEmpty(interceptorRecorder.Diagnostics), userMessage: interceptorRecorder.Diagnostics);
+ Assert.True(interceptorRecorder.WasCalled, userMessage: "No interception code invoked");
+ Assert.True(string.IsNullOrEmpty(interceptorRecorder.Diagnostics), userMessage: $"Expected no diagnostics from interceptorRecorder. Actual: {interceptorRecorder.Diagnostics}");
return (TResult)result!;
}
@@ -96,6 +98,14 @@ private static string ReadUserSourceCode()
// it's very fragile to get user code cs files into output directory (btw we can't remove them from compilation, because we will use them for assertions)
// so let's simply get back to test\ dir, and try to find Executables.UserCode from there
var testDir = Directory.GetParent(Directory.GetParent(Directory.GetParent(Directory.GetParent(Directory.GetCurrentDirectory())!.FullName)!.FullName)!.FullName);
- return File.ReadAllText(Path.Combine(testDir!.FullName, "Dapper.AOT.Test.Integration.Executables", "UserCode", $"{userTypeName}.cs"));
+
+ var sourceCodeFile = Directory
+ .GetFiles(
+ path: Path.Combine(testDir!.FullName, "Dapper.AOT.Test.Integration.Executables", "UserCode"),
+ searchPattern: $"{userTypeName}.cs",
+ searchOption: SearchOption.AllDirectories)
+ .First();
+
+ return File.ReadAllText(sourceCodeFile);
}
}
\ 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 663a4053..f447d516 100644
--- a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj
+++ b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj
@@ -56,6 +56,7 @@
+
diff --git a/test/Dapper.AOT.Test/Helpers/TestFramework.cs b/test/Dapper.AOT.Test/Helpers/TestFramework.cs
new file mode 100644
index 00000000..107af8a1
--- /dev/null
+++ b/test/Dapper.AOT.Test/Helpers/TestFramework.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Dapper.AOT.Test.Helpers
+{
+ internal static class TestFramework
+ {
+ public static readonly ISet NetVersions
+ = ((Net[])Enum.GetValues(typeof(Net)))
+ .Select(static x => x.ToString())
+ .ToHashSet();
+
+ public static Net DetermineNetVersion()
+ {
+#if NET6_0_OR_GREATER
+ return Net.Net6;
+#endif
+ return Net.Net48;
+ }
+
+ public enum Net
+ {
+ Net48,
+ Net6
+ }
+ }
+}
diff --git a/test/Dapper.AOT.Test/Integration/DateOnlyTimeOnlyTests.cs b/test/Dapper.AOT.Test/Integration/DateOnlyTimeOnlyTests.cs
new file mode 100644
index 00000000..5aa87d6e
--- /dev/null
+++ b/test/Dapper.AOT.Test/Integration/DateOnlyTimeOnlyTests.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Data;
+using System.Data.Common;
+using Dapper.Internal;
+using Xunit;
+
+namespace Dapper.AOT.Test.Integration;
+
+[Collection(SharedPostgresqlClient.Collection)]
+public class DateOnlyTimeOnlyPostgreSqlTests : DateOnlyTimeOnlyTests
+{
+ public DateOnlyTimeOnlyPostgreSqlTests(PostgresqlFixture postgresqlFixture) : base(
+ postgresqlFixture.NpgsqlConnection,
+ $"""
+ CREATE TABLE IF NOT EXISTS date_only_table(
+ type_timestamp timestamp,
+ type_date date,
+ type_time time
+ );
+ TRUNCATE date_only_table;
+
+ INSERT INTO date_only_table (type_timestamp, type_date, type_time)
+ VALUES ('{TimeStampConst.ToString("yyyy-MM-dd HH:mm:ss")}', '2021-01-01', '03:03:03');
+ """)
+ {
+ }
+}
+
+[Collection(SharedMsSqlClient.Collection)]
+public class DateOnlyTimeOnlyMsSqlTests : DateOnlyTimeOnlyTests
+{
+ public DateOnlyTimeOnlyMsSqlTests(MsSqlFixture msSqlFixture) : base(
+ msSqlFixture.MsSqlConnection,
+ $"""
+ IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='date_only_table' and xtype='U')
+ BEGIN
+ CREATE TABLE date_only_table(
+ type_timestamp datetime,
+ type_date date,
+ type_time time
+ );
+ END;
+ TRUNCATE TABLE date_only_table;
+
+ INSERT INTO date_only_table (type_timestamp, type_date, type_time)
+ VALUES ('2020-02-02 02:02:02', '2021-01-01', '03:03:03');
+ """)
+ {
+ }
+}
+
+public abstract class DateOnlyTimeOnlyTests
+{
+ protected static DateTime TimeStampConst = new DateTime(year: 2020, month: 2, day: 2, hour: 2, minute: 2, second: 2, kind: DateTimeKind.Utc);
+
+#if NET6_0_OR_GREATER
+ static DateOnly DateOnlyConst = new DateOnly(year: 2021, month: 1, day: 1);
+ static TimeOnly TimeOnlyConst = new TimeOnly(hour: 3, minute: 3, second: 3);
+#endif
+
+ readonly IDbConnection _dbConnection;
+
+ public DateOnlyTimeOnlyTests(IDbConnection dbConnection, string initSql)
+ {
+ _dbConnection = dbConnection;
+ _dbConnection.Execute(initSql);
+ }
+
+ [Fact]
+ public void ReadTimestamp()
+ {
+ using var cmd = _dbConnection.CreateCommand();
+ cmd.CommandText = "select type_timestamp from date_only_table";
+ using var reader = cmd.ExecuteReader();
+
+ var readResult = reader.Read();
+ Assert.True(readResult);
+
+ // DbReader will return `DateTime` here
+ var value = reader.GetValue(0);
+
+ var dateTime = CommandUtils.As(value);
+ Assert.Equal(TimeStampConst, dateTime);
+
+#if NET6_0_OR_GREATER
+ // DateTime has Date and Time, so it can be cast to DateOnly and TimeOnly
+ var dateOnly = CommandUtils.As(value);
+ Assert.Equal(DateOnly.FromDateTime(TimeStampConst), dateOnly);
+
+ var timeOnly = CommandUtils.As(value);
+ Assert.Equal(TimeOnly.FromDateTime(TimeStampConst), timeOnly);
+#endif
+ }
+
+ [Fact]
+ public void ReadDateOnly()
+ {
+ using var cmd = _dbConnection.CreateCommand();
+ cmd.CommandText = "select type_date from date_only_table";
+ using var reader = cmd.ExecuteReader();
+
+ var readResult = reader.Read();
+ Assert.True(readResult);
+
+ // DbReader will return `DateTime` here
+ var value = reader.GetValue(0);
+
+ // technically, date can be interpreted as DateTime.
+ // so lets ensure it is possible to cast to DateTime with default time, but existing year-month-day
+ var dateTime = CommandUtils.As(value);
+ Assert.Equal(new DateTime(year: 2021, month: 1, day: 1), dateTime);
+
+#if NET6_0_OR_GREATER
+ var dateOnly = CommandUtils.As(value);
+ Assert.Equal(DateOnlyConst, dateOnly);
+
+ // it is possible to cast, but it returns default timeOnly, because there is no "time" in actual data
+ // not sure if we need to explicitly throw here to show it's not actual time
+ var timeOnly = CommandUtils.As(value);
+ Assert.Equal(default(TimeOnly), timeOnly);
+#endif
+ }
+
+ [Fact]
+ public void ReadTimeOnly()
+ {
+ using var cmd = _dbConnection.CreateCommand();
+ cmd.CommandText = "select type_time from date_only_table";
+ using var reader = cmd.ExecuteReader();
+
+ var readResult = reader.Read();
+ Assert.True(readResult);
+
+ // DbReader will return `TimeSpan` here
+ var value = reader.GetValue(0);
+
+ // TimeSpan is not a DateTime, so lets ensure it throws on casting to DateTime
+ Assert.Throws(() => CommandUtils.As(value));
+
+#if NET6_0_OR_GREATER
+ // we cant cast TimeSpan to DateOnly - therefore it's expected to throw
+ Assert.Throws(() => CommandUtils.As(value));
+
+ var timeOnly = CommandUtils.As(value);
+ Assert.Equal(TimeOnlyConst, timeOnly);
+#endif
+ }
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test/Integration/MsSqlFixture.cs b/test/Dapper.AOT.Test/Integration/MsSqlFixture.cs
new file mode 100644
index 00000000..ac318a5f
--- /dev/null
+++ b/test/Dapper.AOT.Test/Integration/MsSqlFixture.cs
@@ -0,0 +1,55 @@
+using Microsoft.Data.SqlClient;
+using System.Data;
+using System.Threading.Tasks;
+using Testcontainers.MsSql;
+using Xunit;
+
+namespace Dapper.AOT.Test.Integration;
+
+[CollectionDefinition(Collection)]
+public class SharedMsSqlClient : ICollectionFixture
+{
+ public const string Collection = nameof(SharedMsSqlClient);
+}
+
+public sealed class MsSqlFixture : IAsyncLifetime
+{
+ private readonly MsSqlContainer _msSqlContainer = new MsSqlBuilder()
+ .Build();
+
+ public string ConnectionString { get; private set; } = null!;
+
+ private SqlConnection? _sharedConnection;
+
+ public SqlConnection MsSqlConnection
+ => _sharedConnection ??= CreateOpenConnection();
+
+ public SqlConnection CreateOpenConnection()
+ {
+ var conn = new SqlConnection(ConnectionString);
+ conn.Open();
+ return conn;
+ }
+
+ async Task IAsyncLifetime.InitializeAsync()
+ {
+ await _msSqlContainer.StartAsync();
+ ConnectionString = _msSqlContainer.GetConnectionString();
+ }
+
+ async Task IAsyncLifetime.DisposeAsync()
+ {
+ await using (_msSqlContainer)
+ {
+ var tmp = _sharedConnection;
+ _sharedConnection = null;
+ if (tmp is not null)
+ {
+ tmp.Close();
+#if NET6_0_OR_GREATER
+ await tmp.DisposeAsync();
+#endif
+ }
+ }
+ }
+}
diff --git a/test/Dapper.AOT.Test/InterceptorTests.cs b/test/Dapper.AOT.Test/InterceptorTests.cs
index 79bfe4e6..2f863dad 100644
--- a/test/Dapper.AOT.Test/InterceptorTests.cs
+++ b/test/Dapper.AOT.Test/InterceptorTests.cs
@@ -1,4 +1,5 @@
-using Dapper.CodeAnalysis;
+using Dapper.AOT.Test.Helpers;
+using Dapper.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.IO;
@@ -14,10 +15,33 @@ public class InterceptorTests : GeneratorTestBase
{
public InterceptorTests(ITestOutputHelper log) : base(log) { }
- public static IEnumerable