diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj
index 98d8f11eb..af5febfb8 100644
--- a/Dapper/Dapper.csproj
+++ b/Dapper/Dapper.csproj
@@ -5,7 +5,7 @@
orm;sql;micro-orm
A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc. Major Sponsor: Dapper Plus from ZZZ Projects.
Sam Saffron;Marc Gravell;Nick Craver
- net461;netstandard2.0;net8.0;net10.0
+ net461;netstandard2.0;net8.0
enable
true
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 8787a7767..edda4502c 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,6 +1,10 @@
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
@@ -46,6 +50,7 @@
+
diff --git a/global.json b/global.json
index f7b5e40c2..7da276347 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "10.0.102",
+ "version": "8.0.100",
"rollForward": "latestMajor"
}
}
\ No newline at end of file
diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj
index e02bb4ba3..a7464d32e 100644
--- a/tests/Dapper.Tests/Dapper.Tests.csproj
+++ b/tests/Dapper.Tests/Dapper.Tests.csproj
@@ -2,7 +2,7 @@
Dapper.Tests
Dapper Core Test Suite
- net481;net8.0;net10.0
+ net481;net8.0
$(DefineConstants);MSSQLCLIENT
$(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208;CA1861
enable
@@ -14,6 +14,10 @@
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
@@ -27,12 +31,13 @@
+
-
+
diff --git a/tests/Dapper.Tests/FakeDbTests.Async.cs b/tests/Dapper.Tests/FakeDbTests.Async.cs
new file mode 100644
index 000000000..0a2b5a3c2
--- /dev/null
+++ b/tests/Dapper.Tests/FakeDbTests.Async.cs
@@ -0,0 +1,152 @@
+#if !NET481
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using pengdows.crud.fakeDb;
+using Xunit;
+
+namespace Dapper.Tests
+{
+ public class FakeDbAsyncTests
+ {
+ [Fact]
+ public async Task QueryAsync_MapsColumnsToProperties()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 }, { "Name", "Alice" } }
+ });
+ conn.Open();
+
+ var result = (await conn.QueryAsync("SELECT Id, Name FROM Users")).ToList();
+
+ Assert.Single(result);
+ Assert.Equal(1, result[0].Id);
+ Assert.Equal("Alice", result[0].Name);
+ }
+
+ [Fact]
+ public async Task QueryAsync_ReturnsMultipleRows()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 }, { "Name", "Alice" } },
+ new Dictionary { { "Id", 2 }, { "Name", "Bob" } },
+ });
+ conn.Open();
+
+ var result = (await conn.QueryAsync("SELECT Id, Name FROM Users")).ToList();
+
+ Assert.Equal(2, result.Count);
+ }
+
+ [Fact]
+ public async Task QueryFirstAsync_ReturnsFirstRow()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 5 }, { "Name", "First" } },
+ new Dictionary { { "Id", 6 }, { "Name", "Second" } },
+ });
+ conn.Open();
+
+ var result = await conn.QueryFirstAsync("SELECT Id, Name FROM Users");
+
+ Assert.Equal(5, result.Id);
+ }
+
+ [Fact]
+ public async Task QueryFirstAsync_ThrowsOnEmpty()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(Array.Empty>());
+ conn.Open();
+
+ await Assert.ThrowsAsync(() =>
+ conn.QueryFirstAsync("SELECT Id, Name FROM Users"));
+ }
+
+ [Fact]
+ public async Task QueryFirstOrDefaultAsync_ReturnsNull_WhenEmpty()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(Array.Empty>());
+ conn.Open();
+
+ var result = await conn.QueryFirstOrDefaultAsync("SELECT Id, Name FROM Users");
+
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public async Task QuerySingleAsync_ReturnsRow_WhenExactlyOne()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 7 }, { "Name", "Solo" } }
+ });
+ conn.Open();
+
+ var result = await conn.QuerySingleAsync("SELECT Id, Name FROM Users WHERE Id = 7");
+
+ Assert.Equal(7, result.Id);
+ }
+
+ [Fact]
+ public async Task QuerySingleAsync_ThrowsOnMultipleRows()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 }, { "Name", "A" } },
+ new Dictionary { { "Id", 2 }, { "Name", "B" } },
+ });
+ conn.Open();
+
+ await Assert.ThrowsAsync(() =>
+ conn.QuerySingleAsync("SELECT Id, Name FROM Users"));
+ }
+
+ [Fact]
+ public async Task QuerySingleOrDefaultAsync_ReturnsNull_WhenEmpty()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(Array.Empty>());
+ conn.Open();
+
+ var result = await conn.QuerySingleOrDefaultAsync("SELECT Id, Name FROM Users");
+
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_ReturnsAffectedRowCount()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueNonQueryResult(5);
+ conn.Open();
+
+ var rows = await conn.ExecuteAsync("DELETE FROM Users");
+
+ Assert.Equal(5, rows);
+ }
+
+ [Fact]
+ public async Task ExecuteScalarAsync_ReturnsPreloadedValue()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueScalarResult(99L);
+ conn.Open();
+
+ var count = await conn.ExecuteScalarAsync("SELECT COUNT(*) FROM Users");
+
+ Assert.Equal(99L, count);
+ }
+ }
+}
+#endif
diff --git a/tests/Dapper.Tests/FakeDbTests.AsyncCommandDef.cs b/tests/Dapper.Tests/FakeDbTests.AsyncCommandDef.cs
new file mode 100644
index 000000000..b5146e119
--- /dev/null
+++ b/tests/Dapper.Tests/FakeDbTests.AsyncCommandDef.cs
@@ -0,0 +1,295 @@
+#if !NET481
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+using System.Linq;
+using System.Threading.Tasks;
+using pengdows.crud.fakeDb;
+using Xunit;
+
+namespace Dapper.Tests
+{
+ ///
+ /// Tests for async query overloads that take CommandDefinition (dynamic and generic),
+ /// and for GetRowParser/GetRowParser(Type) with DbDataReader and IDataReader.
+ ///
+ public class FakeDbAsyncCommandDefTests
+ {
+ private class User { public int Id { get; set; } public string? Name { get; set; } }
+
+ // ── QueryAsync(CommandDefinition) dynamic overloads ───────────
+
+ [Fact]
+ public async Task QueryAsync_Dynamic_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 }, { "Name", "Alice" } }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ var results = (await conn.QueryAsync(cmd)).ToList();
+
+ Assert.Single(results);
+ Assert.Equal(1, (int)results[0].Id);
+ }
+
+ [Fact]
+ public async Task QueryFirstAsync_Dynamic_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 2 }, { "Name", "Bob" } }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ dynamic row = await conn.QueryFirstAsync(cmd);
+
+ Assert.Equal(2, (int)row.Id);
+ }
+
+ [Fact]
+ public async Task QueryFirstOrDefaultAsync_Dynamic_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 3 }, { "Name", "Carol" } }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ dynamic? row = await conn.QueryFirstOrDefaultAsync(cmd);
+
+ Assert.NotNull(row);
+ Assert.Equal(3, (int)row!.Id);
+ }
+
+ [Fact]
+ public async Task QueryFirstOrDefaultAsync_Dynamic_EmptySet_ReturnsNull()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(Array.Empty>());
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ var row = await conn.QueryFirstOrDefaultAsync(cmd);
+
+ Assert.Null(row);
+ }
+
+ [Fact]
+ public async Task QuerySingleAsync_Dynamic_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 4 }, { "Name", "Dave" } }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ dynamic row = await conn.QuerySingleAsync(cmd);
+
+ Assert.Equal(4, (int)row.Id);
+ }
+
+ [Fact]
+ public async Task QuerySingleOrDefaultAsync_Dynamic_CommandDefinition_EmptySet_ReturnsNull()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(Array.Empty>());
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ var row = await conn.QuerySingleOrDefaultAsync(cmd);
+
+ Assert.Null(row);
+ }
+
+ // ── QueryAsync(string, ...) dynamic overloads ─────────────────
+
+ [Fact]
+ public async Task QueryAsync_Dynamic_StringSql_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 5 } },
+ new Dictionary { { "Id", 6 } },
+ });
+ conn.Open();
+
+ var results = (await conn.QueryAsync("SELECT Id FROM T")).ToList();
+
+ Assert.Equal(2, results.Count);
+ }
+
+ [Fact]
+ public async Task QueryFirstAsync_Dynamic_StringSql_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 7 } }
+ });
+ conn.Open();
+
+ dynamic row = await conn.QueryFirstAsync("SELECT Id FROM T");
+ Assert.Equal(7, (int)row.Id);
+ }
+
+ [Fact]
+ public async Task QueryFirstOrDefaultAsync_Dynamic_StringSql_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 8 } }
+ });
+ conn.Open();
+
+ dynamic? row = await conn.QueryFirstOrDefaultAsync("SELECT Id FROM T");
+ Assert.NotNull(row);
+ }
+
+ [Fact]
+ public async Task QuerySingleAsync_Dynamic_StringSql_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 9 } }
+ });
+ conn.Open();
+
+ dynamic row = await conn.QuerySingleAsync("SELECT Id FROM T");
+ Assert.Equal(9, (int)row.Id);
+ }
+
+ [Fact]
+ public async Task QuerySingleOrDefaultAsync_Dynamic_StringSql_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 10 } }
+ });
+ conn.Open();
+
+ dynamic? row = await conn.QuerySingleOrDefaultAsync("SELECT Id FROM T");
+ Assert.NotNull(row);
+ }
+
+ // ── Async multimap with Type[] ─────────────────────────────────
+
+ private class Owner { public int Id { get; set; } public string? Name { get; set; } }
+ private class Pet { public int PetId { get; set; } public string? Breed { get; set; } }
+
+ [Fact]
+ public async Task QueryAsync_TypeArray_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary {
+ { "Id", 1 }, { "Name", "Alice" },
+ { "PetId", 10 }, { "Breed", "Lab" }
+ }
+ });
+ conn.Open();
+
+ var results = (await conn.QueryAsync<(Owner, Pet)>(
+ "SELECT ...",
+ new[] { typeof(Owner), typeof(Pet) },
+ objs => ((Owner)objs[0], (Pet)objs[1]),
+ splitOn: "PetId")).ToList();
+
+ Assert.Single(results);
+ Assert.Equal("Alice", results[0].Item1.Name);
+ }
+
+ // ── GetRowParser(IDataReader, Type) ───────────────────────────
+
+ [Fact]
+ public void IDataReader_GetRowParser_ByType_Specific_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 11 }, { "Name", "Eve" } }
+ });
+ conn.Open();
+
+ using IDataReader reader = conn.ExecuteReader("SELECT Id, Name FROM Users");
+ var parser = reader.GetRowParser(typeof(User));
+
+ Assert.True(reader.Read());
+ var user = (User)parser(reader);
+ Assert.Equal(11, user.Id);
+ }
+
+ [Fact]
+ public void DbDataReader_GetRowParser_ByType_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 12 }, { "Name", "Frank" } }
+ });
+ conn.Open();
+
+ using DbDataReader dbReader = (DbDataReader)conn.ExecuteReader("SELECT Id, Name FROM Users");
+ var parser = dbReader.GetRowParser(typeof(User));
+
+ Assert.True(dbReader.Read());
+ var user = (User)parser(dbReader);
+ Assert.Equal(12, user.Id);
+ }
+
+ [Fact]
+ public void DbDataReader_GetRowParser_ValueType_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Item1", 99 } }
+ });
+ conn.Open();
+
+ using DbDataReader dbReader = (DbDataReader)conn.ExecuteReader("SELECT 99");
+ // GetRowParser with value type takes a different code path (IsValueType branch)
+ var parser = dbReader.GetRowParser();
+
+ Assert.True(dbReader.Read());
+ var val = parser(dbReader);
+ Assert.Equal(99, val);
+ }
+
+ // ── QueryCachePurged event ────────────────────────────────────
+
+ [Fact]
+ public void PurgeQueryCache_FiresEvent_WhenSubscribed()
+ {
+ bool fired = false;
+ SqlMapper.QueryCachePurged += OnPurged;
+ try
+ {
+ SqlMapper.PurgeQueryCache();
+ Assert.True(fired);
+ }
+ finally
+ {
+ SqlMapper.QueryCachePurged -= OnPurged;
+ }
+
+ void OnPurged(object? sender, EventArgs e) => fired = true;
+ }
+ }
+}
+#endif
diff --git a/tests/Dapper.Tests/FakeDbTests.AsyncEnumerable.cs b/tests/Dapper.Tests/FakeDbTests.AsyncEnumerable.cs
new file mode 100644
index 000000000..17ce9aac3
--- /dev/null
+++ b/tests/Dapper.Tests/FakeDbTests.AsyncEnumerable.cs
@@ -0,0 +1,138 @@
+#if !NET481
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using pengdows.crud.fakeDb;
+using Xunit;
+
+namespace Dapper.Tests
+{
+ ///
+ /// Tests for QueryUnbufferedAsync (IAsyncEnumerable) and related async enumerable paths.
+ ///
+ public class FakeDbAsyncEnumerableTests
+ {
+ private class User { public int Id { get; set; } public string? Name { get; set; } }
+
+ [Fact]
+ public async Task QueryUnbufferedAsync_SingleRow_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 }, { "Name", "Alice" } }
+ });
+ conn.Open();
+
+ var results = new List();
+ await foreach (var item in conn.QueryUnbufferedAsync("SELECT Id, Name FROM Users"))
+ {
+ results.Add(item);
+ }
+
+ Assert.Single(results);
+ Assert.Equal(1, results[0].Id);
+ Assert.Equal("Alice", results[0].Name);
+ }
+
+ [Fact]
+ public async Task QueryUnbufferedAsync_MultipleRows_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 }, { "Name", "Alice" } },
+ new Dictionary { { "Id", 2 }, { "Name", "Bob" } },
+ new Dictionary { { "Id", 3 }, { "Name", "Carol" } },
+ });
+ conn.Open();
+
+ var results = new List();
+ await foreach (var item in conn.QueryUnbufferedAsync("SELECT Id, Name FROM Users"))
+ {
+ results.Add(item);
+ }
+
+ Assert.Equal(3, results.Count);
+ Assert.Equal("Alice", results[0].Name);
+ Assert.Equal("Bob", results[1].Name);
+ Assert.Equal("Carol", results[2].Name);
+ }
+
+ [Fact]
+ public async Task QueryUnbufferedAsync_EmptyResult_ReturnsNothing()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(System.Array.Empty>());
+ conn.Open();
+
+ var results = new List();
+ await foreach (var item in conn.QueryUnbufferedAsync("SELECT Id, Name FROM Users"))
+ {
+ results.Add(item);
+ }
+
+ Assert.Empty(results);
+ }
+
+ [Fact]
+ public async Task QueryUnbufferedAsync_WithParameters_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 5 }, { "Name", "Dave" } }
+ });
+ conn.Open();
+
+ var results = new List();
+ await foreach (var item in conn.QueryUnbufferedAsync(
+ "SELECT Id, Name FROM Users WHERE Id = @id", new { id = 5 }))
+ {
+ results.Add(item);
+ }
+
+ Assert.Single(results);
+ Assert.Equal(5, results[0].Id);
+ }
+
+ [Fact]
+ public async Task QueryUnbufferedAsync_CollectAll_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 }, { "Name", "A" } },
+ new Dictionary { { "Id", 2 }, { "Name", "B" } },
+ });
+ conn.Open();
+
+ var results = new List();
+ await foreach (var item in conn.QueryUnbufferedAsync("SELECT Id, Name FROM Users"))
+ results.Add(item);
+
+ Assert.Equal(2, results.Count);
+ }
+
+ [Fact]
+ public async Task QueryUnbufferedAsync_Dynamic_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 7 }, { "Name", "Eve" } }
+ });
+ conn.Open();
+
+ var results = new List();
+ await foreach (var item in conn.QueryUnbufferedAsync("SELECT Id, Name FROM T"))
+ {
+ results.Add(item);
+ }
+
+ Assert.Single(results);
+ Assert.Equal(7, (int)results[0].Id);
+ }
+ }
+}
+#endif
diff --git a/tests/Dapper.Tests/FakeDbTests.AsyncMultimap.cs b/tests/Dapper.Tests/FakeDbTests.AsyncMultimap.cs
new file mode 100644
index 000000000..0132079c3
--- /dev/null
+++ b/tests/Dapper.Tests/FakeDbTests.AsyncMultimap.cs
@@ -0,0 +1,378 @@
+#if !NET481
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using pengdows.crud.fakeDb;
+using Xunit;
+
+namespace Dapper.Tests
+{
+ ///
+ /// Tests for async multimap 3/4/5/6/7-type variants (string SQL and CommandDefinition),
+ /// and QueryAsync/QueryFirstAsync/QuerySingleAsync with Type+CommandDefinition overloads.
+ ///
+ public class FakeDbAsyncMultimapTests
+ {
+ private class A { public int Id { get; set; } public string? Name { get; set; } }
+ private class B { public int BId { get; set; } public string? BName { get; set; } }
+ private class C { public int CId { get; set; } }
+ private class D { public int DId { get; set; } }
+ private class E { public int EId { get; set; } }
+ private class F { public int FId { get; set; } }
+ private class G { public int GId { get; set; } }
+
+ private static Dictionary MakeRow7() => new()
+ {
+ { "Id", 1 }, { "Name", "Alice" },
+ { "BId", 2 }, { "BName", "Brow" },
+ { "CId", 3 }, { "DId", 4 }, { "EId", 5 }, { "FId", 6 }, { "GId", 7 }
+ };
+
+ // ── QueryAsync(Type, CommandDefinition) ───────────────────────
+
+ [Fact]
+ public async Task QueryAsync_Type_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 }, { "Name", "Alice" } }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ var results = (await conn.QueryAsync(typeof(A), cmd)).Cast().ToList();
+
+ Assert.Single(results);
+ Assert.Equal(1, results[0].Id);
+ }
+
+ [Fact]
+ public async Task QueryFirstAsync_Type_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 2 }, { "Name", "Bob" } }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ var row = (A)await conn.QueryFirstAsync(typeof(A), cmd);
+
+ Assert.Equal(2, row.Id);
+ }
+
+ [Fact]
+ public async Task QueryFirstOrDefaultAsync_Type_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 3 }, { "Name", "Carol" } }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ var row = await conn.QueryFirstOrDefaultAsync(typeof(A), cmd);
+
+ Assert.NotNull(row);
+ Assert.Equal(3, ((A)row!).Id);
+ }
+
+ [Fact]
+ public async Task QueryFirstOrDefaultAsync_Type_CommandDefinition_Empty_ReturnsNull()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(Array.Empty>());
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ var row = await conn.QueryFirstOrDefaultAsync(typeof(A), cmd);
+
+ Assert.Null(row);
+ }
+
+ [Fact]
+ public async Task QuerySingleAsync_Type_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 4 }, { "Name", "Dave" } }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ var row = (A)await conn.QuerySingleAsync(typeof(A), cmd);
+
+ Assert.Equal(4, row.Id);
+ }
+
+ [Fact]
+ public async Task QuerySingleOrDefaultAsync_Type_CommandDefinition_Empty_ReturnsNull()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(Array.Empty>());
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T");
+ var row = await conn.QuerySingleOrDefaultAsync(typeof(A), cmd);
+
+ Assert.Null(row);
+ }
+
+ // ── Async 3-type CommandDefinition ────────────────────────────
+
+ [Fact]
+ public async Task QueryAsync_3Types_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary {
+ { "Id", 1 }, { "Name", "Alice" },
+ { "BId", 2 }, { "BName", "B" },
+ { "CId", 3 }
+ }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT ...");
+ var results = (await conn.QueryAsync(
+ cmd,
+ (a, b, c) => $"{a.Id}-{b.BId}-{c.CId}",
+ splitOn: "BId,CId"
+ )).ToList();
+
+ Assert.Single(results);
+ Assert.Equal("1-2-3", results[0]);
+ }
+
+ // ── Async 4-type string SQL ───────────────────────────────────
+
+ [Fact]
+ public async Task QueryAsync_4Types_StringSql_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary {
+ { "Id", 1 }, { "Name", "Alice" },
+ { "BId", 2 }, { "BName", "B" },
+ { "CId", 3 }, { "DId", 4 }
+ }
+ });
+ conn.Open();
+
+ var results = (await conn.QueryAsync(
+ "SELECT ...",
+ (a, b, c, d) => $"{a.Id}-{b.BId}-{c.CId}-{d.DId}",
+ splitOn: "BId,CId,DId"
+ )).ToList();
+
+ Assert.Single(results);
+ Assert.Equal("1-2-3-4", results[0]);
+ }
+
+ // ── Async 4-type CommandDefinition ────────────────────────────
+
+ [Fact]
+ public async Task QueryAsync_4Types_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary {
+ { "Id", 1 }, { "Name", "Alice" },
+ { "BId", 2 }, { "BName", "B" },
+ { "CId", 3 }, { "DId", 4 }
+ }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT ...");
+ var results = (await conn.QueryAsync(
+ cmd,
+ (a, b, c, d) => $"{a.Id}-{b.BId}-{c.CId}-{d.DId}",
+ splitOn: "BId,CId,DId"
+ )).ToList();
+
+ Assert.Single(results);
+ Assert.Equal("1-2-3-4", results[0]);
+ }
+
+ // ── Async 5-type string SQL ───────────────────────────────────
+
+ [Fact]
+ public async Task QueryAsync_5Types_StringSql_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary {
+ { "Id", 1 }, { "Name", "X" },
+ { "BId", 2 }, { "BName", "B" },
+ { "CId", 3 }, { "DId", 4 }, { "EId", 5 }
+ }
+ });
+ conn.Open();
+
+ var results = (await conn.QueryAsync(
+ "SELECT ...",
+ (a, b, c, d, e) => $"{a.Id}-{e.EId}",
+ splitOn: "BId,CId,DId,EId"
+ )).ToList();
+
+ Assert.Single(results);
+ Assert.Equal("1-5", results[0]);
+ }
+
+ // ── Async 5-type CommandDefinition ────────────────────────────
+
+ [Fact]
+ public async Task QueryAsync_5Types_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary {
+ { "Id", 1 }, { "Name", "X" },
+ { "BId", 2 }, { "BName", "B" },
+ { "CId", 3 }, { "DId", 4 }, { "EId", 5 }
+ }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT ...");
+ var results = (await conn.QueryAsync(
+ cmd,
+ (a, b, c, d, e) => $"{a.Id}-{e.EId}",
+ splitOn: "BId,CId,DId,EId"
+ )).ToList();
+
+ Assert.Single(results);
+ Assert.Equal("1-5", results[0]);
+ }
+
+ // ── Async 6-type string SQL ───────────────────────────────────
+
+ [Fact]
+ public async Task QueryAsync_6Types_StringSql_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary {
+ { "Id", 1 }, { "Name", "X" },
+ { "BId", 2 }, { "BName", "B" },
+ { "CId", 3 }, { "DId", 4 }, { "EId", 5 }, { "FId", 6 }
+ }
+ });
+ conn.Open();
+
+ var results = (await conn.QueryAsync(
+ "SELECT ...",
+ (a, b, c, d, e, f) => $"{a.Id}-{f.FId}",
+ splitOn: "BId,CId,DId,EId,FId"
+ )).ToList();
+
+ Assert.Single(results);
+ Assert.Equal("1-6", results[0]);
+ }
+
+ // ── Async 6-type CommandDefinition ────────────────────────────
+
+ [Fact]
+ public async Task QueryAsync_6Types_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary {
+ { "Id", 1 }, { "Name", "X" },
+ { "BId", 2 }, { "BName", "B" },
+ { "CId", 3 }, { "DId", 4 }, { "EId", 5 }, { "FId", 6 }
+ }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT ...");
+ var results = (await conn.QueryAsync(
+ cmd,
+ (a, b, c, d, e, f) => $"{a.Id}-{f.FId}",
+ splitOn: "BId,CId,DId,EId,FId"
+ )).ToList();
+
+ Assert.Single(results);
+ Assert.Equal("1-6", results[0]);
+ }
+
+ // ── Async 7-type string SQL ───────────────────────────────────
+
+ [Fact]
+ public async Task QueryAsync_7Types_StringSql_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[] { MakeRow7() });
+ conn.Open();
+
+ var results = (await conn.QueryAsync(
+ "SELECT ...",
+ (a, b, c, d, e, f, g) => $"{a.Id}-{g.GId}",
+ splitOn: "BId,CId,DId,EId,FId,GId"
+ )).ToList();
+
+ Assert.Single(results);
+ Assert.Equal("1-7", results[0]);
+ }
+
+ // ── Async 7-type CommandDefinition ────────────────────────────
+
+ [Fact]
+ public async Task QueryAsync_7Types_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[] { MakeRow7() });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT ...");
+ var results = (await conn.QueryAsync(
+ cmd,
+ (a, b, c, d, e, f, g) => $"{a.Id}-{g.GId}",
+ splitOn: "BId,CId,DId,EId,FId,GId"
+ )).ToList();
+
+ Assert.Single(results);
+ Assert.Equal("1-7", results[0]);
+ }
+
+ // ── Async 2-type CommandDefinition ────────────────────────────
+
+ [Fact]
+ public async Task QueryAsync_2Types_CommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary {
+ { "Id", 1 }, { "Name", "Alice" },
+ { "BId", 2 }, { "BName", "B" }
+ }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT ...");
+ var results = (await conn.QueryAsync(
+ cmd,
+ (a, b) => $"{a.Id}-{b.BId}",
+ splitOn: "BId"
+ )).ToList();
+
+ Assert.Single(results);
+ Assert.Equal("1-2", results[0]);
+ }
+ }
+}
+#endif
diff --git a/tests/Dapper.Tests/FakeDbTests.AsyncTypeOverloads.cs b/tests/Dapper.Tests/FakeDbTests.AsyncTypeOverloads.cs
new file mode 100644
index 000000000..fac0d7b65
--- /dev/null
+++ b/tests/Dapper.Tests/FakeDbTests.AsyncTypeOverloads.cs
@@ -0,0 +1,255 @@
+#if !NET481
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using pengdows.crud.fakeDb;
+using Xunit;
+
+namespace Dapper.Tests
+{
+ ///
+ /// Tests for async overloads that take a runtime Type parameter,
+ /// XElement parameter handling, DataTableHandler.Parse exception,
+ /// TableValuedParameter.AddParameter, and more SqlMapper coverage.
+ ///
+ public class FakeDbAsyncTypeOverloadTests
+ {
+ private class User { public int Id { get; set; } public string? Name { get; set; } }
+
+ // ── QueryFirstAsync(Type) ─────────────────────────────────────
+
+ [Fact]
+ public async Task QueryFirstAsync_ByType_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 }, { "Name", "Alice" } }
+ });
+ conn.Open();
+
+ var row = await conn.QueryFirstAsync(typeof(User), "SELECT Id, Name FROM T");
+ Assert.Equal(1, ((User)row).Id);
+ }
+
+ [Fact]
+ public async Task QueryFirstOrDefaultAsync_ByType_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 2 }, { "Name", "Bob" } }
+ });
+ conn.Open();
+
+ var row = await conn.QueryFirstOrDefaultAsync(typeof(User), "SELECT Id, Name FROM T");
+ Assert.NotNull(row);
+ Assert.Equal(2, ((User)row!).Id);
+ }
+
+ [Fact]
+ public async Task QueryFirstOrDefaultAsync_ByType_EmptySet_ReturnsNull()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(Array.Empty>());
+ conn.Open();
+
+ var row = await conn.QueryFirstOrDefaultAsync(typeof(User), "SELECT Id, Name FROM T");
+ Assert.Null(row);
+ }
+
+ [Fact]
+ public async Task QuerySingleAsync_ByType_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 3 }, { "Name", "Carol" } }
+ });
+ conn.Open();
+
+ var row = await conn.QuerySingleAsync(typeof(User), "SELECT Id, Name FROM T");
+ Assert.Equal(3, ((User)row).Id);
+ }
+
+ [Fact]
+ public async Task QuerySingleOrDefaultAsync_ByType_EmptySet_ReturnsNull()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(Array.Empty>());
+ conn.Open();
+
+ var row = await conn.QuerySingleOrDefaultAsync(typeof(User), "SELECT Id, Name FROM T");
+ Assert.Null(row);
+ }
+
+ // ── XElement as parameter (covers XElementHandler.SetValue + Format) ──
+
+ [Fact]
+ public void Execute_WithXElementParam_Works()
+ {
+ var element = new XElement("root", new XElement("child", "value"));
+
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueNonQueryResult(1);
+ conn.Open();
+
+ // Passing XElement triggers XElementHandler.SetValue and Format
+ conn.Execute("UPDATE T SET Xml = @xml WHERE Id = 1", new { xml = element });
+ }
+
+ [Fact]
+ public void Query_WithXElementParam_Works()
+ {
+ var element = new XElement("filter", "value");
+
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 } }
+ });
+ conn.Open();
+
+ var result = conn.Query("SELECT Id FROM T WHERE Xml = @xml", new { xml = element })
+ .ToList();
+ Assert.Single(result);
+ }
+
+ // ── DataTableHandler.Parse throws NotImplementedException ─────
+
+ [Fact]
+ public void DataTableHandler_Parse_Throws_NotImplementedException()
+ {
+ // DataTableHandler.Parse is not implemented and throws NotImplementedException
+ // To trigger it: pass DataTable as target type in Query
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 } }
+ });
+ conn.Open();
+
+ // This should trigger DataTableHandler.Parse which throws NotImplementedException
+ Assert.Throws(() =>
+ conn.Query("SELECT Id FROM T").ToList());
+ }
+
+ // ── TableValuedParameter.AddParameter ─────────────────────────
+
+ [Fact]
+ public void TableValuedParameter_AddParameter_Works()
+ {
+ var dt = new DataTable();
+ dt.Columns.Add("Id", typeof(int));
+ dt.Rows.Add(1);
+ dt.Rows.Add(2);
+
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueNonQueryResult(1);
+ conn.Open();
+
+ // AsTableValuedParameter() returns ICustomQueryParameter
+ // When passed to Dapper, it calls AddParameter
+ var tvp = dt.AsTableValuedParameter();
+ var dp = new DynamicParameters();
+ dp.Add("ids", tvp);
+ conn.Execute("EXEC BulkInsert @ids", dp);
+ }
+
+ // ── CommandDefinition.InferCommandType — StoredProcedure path ─
+
+ [Fact]
+ public void CommandDefinition_InferCommandType_StoredProc()
+ {
+ // A name with no whitespace/special chars is inferred as StoredProcedure
+ var cmd = new CommandDefinition("sp_GetUser"); // no whitespace -> StoredProc
+ Assert.Equal(CommandType.StoredProcedure, cmd.CommandTypeDirect);
+ }
+
+ [Fact]
+ public void CommandDefinition_InferCommandType_Text()
+ {
+ var cmd = new CommandDefinition("SELECT 1"); // has whitespace -> Text
+ Assert.Equal(CommandType.Text, cmd.CommandTypeDirect);
+ }
+
+ // ── CollectCacheGarbage path ───────────────────────────────────
+ // Triggered after COLLECT_PER_ITEMS (1000) distinct queries
+
+ // ── DefaultTypeMap additional coverage ────────────────────────
+
+ private class TypeWithField { public int Id; }
+
+ [Fact]
+ public void DefaultTypeMap_GetSettableFields_ReturnsFields()
+ {
+ var fields = DefaultTypeMap.GetSettableFields(typeof(TypeWithField));
+ Assert.NotEmpty(fields);
+ }
+
+ [Fact]
+ public void DefaultTypeMap_GetSettableProps_ReturnsProps()
+ {
+ var props = DefaultTypeMap.GetSettableProps(typeof(User));
+ Assert.Equal(2, props.Count);
+ }
+
+ // ── Additional SqlMapper coverage: GetCachedSQLCount ──────────
+
+ [Fact]
+ public void GetCachedSQLCount_ReturnsNonNegative()
+ {
+ var count = SqlMapper.GetCachedSQLCount();
+ Assert.True(count >= 0);
+ }
+
+ // ── ExecuteScalarAsync(CommandDefinition) with timeout ─────────
+
+ [Fact]
+ public async Task ExecuteScalarAsync_WithCommandDefinition_Works()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Result", 42 } }
+ });
+ conn.Open();
+
+ var cmd = new CommandDefinition("SELECT 42", commandTimeout: 5);
+ var result = await conn.ExecuteScalarAsync(cmd);
+ Assert.Equal(42, result);
+ }
+
+ // ── Query with CommandTimeout from Settings ────────────────────
+
+ [Fact]
+ public void Query_WithSettingsCommandTimeout_Works()
+ {
+ var original = SqlMapper.Settings.CommandTimeout;
+ try
+ {
+ SqlMapper.Settings.CommandTimeout = 30;
+
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.EnqueueReaderResult(new[]
+ {
+ new Dictionary { { "Id", 1 }, { "Name", "Alice" } }
+ });
+ conn.Open();
+
+ // This exercises the Settings.CommandTimeout fallback in SetupCommand
+ var cmd = new CommandDefinition("SELECT Id, Name FROM T"); // no explicit timeout
+ var results = conn.Query(cmd).ToList();
+ Assert.Single(results);
+ }
+ finally
+ {
+ SqlMapper.Settings.CommandTimeout = original;
+ }
+ }
+ }
+}
+#endif
diff --git a/tests/Dapper.Tests/FakeDbTests.BatchExecute.cs b/tests/Dapper.Tests/FakeDbTests.BatchExecute.cs
new file mode 100644
index 000000000..db2b7b097
--- /dev/null
+++ b/tests/Dapper.Tests/FakeDbTests.BatchExecute.cs
@@ -0,0 +1,219 @@
+#if !NET481
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using pengdows.crud.fakeDb;
+using Xunit;
+
+namespace Dapper.Tests
+{
+ public class FakeDbBatchExecuteTests
+ {
+ // ── Execute with IEnumerable (sync batch) ──────────────────
+
+ [Fact]
+ public void Execute_WithList_ExecutesForEach()
+ {
+ var items = new[]
+ {
+ new { id = 1, name = "Alice" },
+ new { id = 2, name = "Bob" },
+ new { id = 3, name = "Carol" },
+ };
+
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ // Enqueue one result per item
+ foreach (var _ in items)
+ conn.EnqueueNonQueryResult(1);
+ conn.Open();
+
+ var total = conn.Execute("INSERT INTO Users (Id, Name) VALUES (@id, @name)", items);
+
+ Assert.Equal(3, total);
+ }
+
+ [Fact]
+ public void Execute_WithEmptyList_ReturnsZero()
+ {
+ using var conn = new fakeDbConnection(new FakeDataStore());
+ conn.Open();
+
+ var total = conn.Execute("INSERT INTO Users VALUES (@id)",
+ Enumerable.Empty