diff --git a/Dapper/SqlMapper.Identity.cs b/Dapper/SqlMapper.Identity.cs index 4871d9c05..1952be4f6 100644 --- a/Dapper/SqlMapper.Identity.cs +++ b/Dapper/SqlMapper.Identity.cs @@ -94,16 +94,20 @@ public class Identity : IEquatable internal virtual Type GetType(int index) => throw new IndexOutOfRangeException(nameof(index)); #pragma warning disable CS0618 // Type or member is obsolete + // Grid (multi-result) reads only build a deserializer and never bind parameters + // (parameters are bound once, against the command-level identity). Carrying parametersType + // here makes a grid-0 read of type T collide with a QueryAsync identity and overwrite its + // ParamReader with a property-based reader (wrong for IDynamicParameters). internal Identity ForGrid(Type primaryType, int gridIndex) => - new Identity(sql, commandType, connectionString, primaryType, parametersType, gridIndex); + new Identity(sql, commandType, connectionString, primaryType, null, gridIndex); internal Identity ForGrid(Type primaryType, int gridIndex) => - new Identity(sql, commandType, connectionString, primaryType, parametersType, 0, gridIndex); + new Identity(sql, commandType, connectionString, primaryType, null, 0, gridIndex); internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) => (otherTypes is null || otherTypes.Length == 0) - ? new Identity(sql, commandType, connectionString, primaryType, parametersType, 0, gridIndex) - : new IdentityWithTypes(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); + ? new Identity(sql, commandType, connectionString, primaryType, null, 0, gridIndex) + : new IdentityWithTypes(sql, commandType, connectionString, primaryType, null, otherTypes, gridIndex); /// /// Create an identity for use with DynamicParameters, internal use only. diff --git a/tests/Dapper.Tests/QueryMultipleTests.cs b/tests/Dapper.Tests/QueryMultipleTests.cs index 8e3015649..440619616 100644 --- a/tests/Dapper.Tests/QueryMultipleTests.cs +++ b/tests/Dapper.Tests/QueryMultipleTests.cs @@ -30,6 +30,73 @@ public void TestQueryMultipleBuffered() Assert.Equal(4, d.Single()); } + // Regression for DapperLib/Dapper#243 and DapperLib/Dapper#2091: + // A QueryMultiple grid-read calls GetCacheInfo(identity, null), so for an IDynamicParameters + // parameter type it builds a property-based ParamReader (via CreateParamInfoGenerator) and + // caches it under an identity for grid 0 of type T which collides with the Query identity. + // A subsequent Query with DynamicParameters reuses that reader and sends no + // real parameters (manifesting as "must declare the scalar variable @x" / + // "@ParameterNames1 is not a parameter"). + [Fact] + public void QueryMultipleThenQueryWithDynamicParameters_DoesNotDropParameters() + { + // two grids, same parameter + const string sql = "select @x as X; select @x as Y;"; + + var first = new DynamicParameters(); + first.Add("x", 1); + using (var grid = connection.QueryMultiple(sql, first)) + { + Assert.Equal(1, grid.Read().Single()); + Assert.Equal(1, grid.Read().Single()); + } + + // Same sql + result type + DynamicParameters as grid 0 above; @x must still be supplied. + var second = new DynamicParameters(); + second.Add("x", 42); + Assert.Equal(42, connection.Query(sql, second).Single()); + } + + // Regression for DapperLib/Dapper#243 and DapperLib/Dapper#2091: + // The reverse order has always worked (Query then QueryMultiple) + [Fact] + public void QueryThenQueryMultipleWithDynamicParameters_DoesNotDropParameters() + { + // distinct sql -> own cache entry (independent of the test above) + const string sql = "select @x as P; select @x as Q;"; + + var first = new DynamicParameters(); + first.Add("x", 7); + Assert.Equal(7, connection.Query(sql, first).Single()); + + var second = new DynamicParameters(); + second.Add("x", 99); + using var grid = connection.QueryMultiple(sql, second); + Assert.Equal(99, grid.Read().Single()); + Assert.Equal(99, grid.Read().Single()); + } + + // Stored-proc form of the same bug (#2091). + [Fact] + public void QueryMultipleThenQueryStoredProc_DoesNotEmitDynamicParametersProperties() + { + connection.Execute("create proc #Repro2091 (@x int = 0, @z int = 0) as begin set nocount on; select @x as X; select @x as Y; end"); + + var first = new DynamicParameters(); + first.Add("x", 1); + using (var grid = connection.QueryMultiple("#Repro2091", first, commandType: CommandType.StoredProcedure)) + { + // grid 0 of seeds the shared identity + Assert.Equal(1, grid.Read().Single()); + Assert.Equal(1, grid.Read().Single()); + } + + var second = new DynamicParameters(); + second.Add("x", 42); + // Without fix expect SqlException "@ParameterNames1 is not a parameter for procedure #Repro2091." + Assert.Equal(42, connection.Query("#Repro2091", second, commandType: CommandType.StoredProcedure).Single()); + } + [Fact] public void TestMultiConversion() {