diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index eade08cb2..07ca478f6 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -940,7 +940,7 @@ private static async Task> MultiMapAsync(null, CommandDefinition.ForCallback(command.Parameters, command.Flags), map, splitOn, reader, identity, true); + var results = MultiMapImpl(null, CommandDefinition.ForCallback(command.Parameters, command.Flags), map, splitOn, reader, identity, true, info); return command.Buffered ? results.ToList() : results; } finally @@ -988,7 +988,7 @@ private static async Task> MultiMapAsync(this IDbC if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); using var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false); - var results = MultiMapImpl(null, default, types, map, splitOn, reader, identity, true); + var results = MultiMapImpl(null, CommandDefinition.ForCallback(command.Parameters, command.Flags), types, map, splitOn, reader, identity, true, info); return command.Buffered ? results.ToList() : results; } finally diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 8f3956cab..f3abebcc5 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1584,11 +1584,11 @@ private static IEnumerable MultiMap MultiMapImpl(this IDbConnection? cnn, CommandDefinition command, Delegate map, string splitOn, DbDataReader? reader, Identity? identity, bool finalize) + private static IEnumerable MultiMapImpl(this IDbConnection? cnn, CommandDefinition command, Delegate map, string splitOn, DbDataReader? reader, Identity? identity, bool finalize, CacheInfo? cacheInfo = null) { object? param = command.Parameters; identity ??= new Identity(command.CommandText, command.CommandTypeDirect, cnn!, typeof(TFirst), param?.GetType()); - CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); + CacheInfo cinfo = cacheInfo ?? GetCacheInfo(identity, param, command.AddToCache); IDbCommand? ownedCommand = null; DbDataReader? ownedReader = null; @@ -1650,7 +1650,7 @@ private static CommandBehavior GetBehavior(bool close, CommandBehavior @default) return (close ? (@default | CommandBehavior.CloseConnection) : @default) & Settings.AllowedCommandBehaviors; } - private static IEnumerable MultiMapImpl(this IDbConnection? cnn, CommandDefinition command, Type[] types, Func map, string splitOn, DbDataReader? reader, Identity? identity, bool finalize) + private static IEnumerable MultiMapImpl(this IDbConnection? cnn, CommandDefinition command, Type[] types, Func map, string splitOn, DbDataReader? reader, Identity? identity, bool finalize, CacheInfo? cacheInfo = null) { if (types.Length < 1) { @@ -1659,7 +1659,7 @@ private static IEnumerable MultiMapImpl(this IDbConnection? cn object? param = command.Parameters; identity ??= new IdentityWithTypes(command.CommandText, command.CommandTypeDirect, cnn!, types[0], param?.GetType(), types); - CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); + CacheInfo cinfo = cacheInfo ?? GetCacheInfo(identity, param, command.AddToCache); IDbCommand? ownedCommand = null; DbDataReader? ownedReader = null; diff --git a/tests/Dapper.Tests/Providers/SqliteTests.cs b/tests/Dapper.Tests/Providers/SqliteTests.cs index b0c5e0f1b..f14e9ebb3 100644 --- a/tests/Dapper.Tests/Providers/SqliteTests.cs +++ b/tests/Dapper.Tests/Providers/SqliteTests.cs @@ -158,5 +158,51 @@ private class PersonWithDob public int Id { get; set; } public DateTime DoB { get; set; } } + + [FactSqlite] + public async Task Issue2240_MultiMapAsync_CacheHitAccounting() + { + using var connection = GetSQLiteConnection(); + SqlMapper.PurgeQueryCache(); + + var sqlGeneric = "SELECT 1 AS Id, 'Alice' AS Name, 2 AS ExtraId, 'Bob' AS ExtraName WHERE 0 /* issue2240_generic */"; + await connection.QueryAsync( + sqlGeneric, + (a, b) => a.Id + b.Id, + splitOn: "ExtraId").ConfigureAwait(false); + + var genericHits = SqlMapper.GetCachedSQL().Single(x => x.Item2 == sqlGeneric).Item3; + Assert.Equal(0, genericHits); + + // Second execution of the same query should count as reuse (hit count 1) + await connection.QueryAsync( + sqlGeneric, + (a, b) => a.Id + b.Id, + splitOn: "ExtraId").ConfigureAwait(false); + + genericHits = SqlMapper.GetCachedSQL().Single(x => x.Item2 == sqlGeneric).Item3; + Assert.Equal(1, genericHits); + + var sqlTypes = "SELECT 1 AS Id, 'Alice' AS Name, 2 AS ExtraId, 'Bob' AS ExtraName WHERE 0 /* issue2240_types */"; + await connection.QueryAsync( + sqlTypes, + new[] { typeof(HazNameId), typeof(HazNameId) }, + objs => ((HazNameId)objs[0]).Id + ((HazNameId)objs[1]).Id, + splitOn: "ExtraId").ConfigureAwait(false); + + var typesHits = SqlMapper.GetCachedSQL().Single(x => x.Item2 == sqlTypes).Item3; + Assert.Equal(0, typesHits); + + // Second execution of the same query should count as reuse (hit count 1) + await connection.QueryAsync( + sqlTypes, + new[] { typeof(HazNameId), typeof(HazNameId) }, + objs => ((HazNameId)objs[0]).Id + ((HazNameId)objs[1]).Id, + splitOn: "ExtraId").ConfigureAwait(false); + + typesHits = SqlMapper.GetCachedSQL().Single(x => x.Item2 == sqlTypes).Item3; + Assert.Equal(1, typesHits); + } } } +