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
12 changes: 8 additions & 4 deletions Dapper/SqlMapper.Identity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,20 @@ public class Identity : IEquatable<Identity>
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<T> identity and overwrite its
// ParamReader with a property-based reader (wrong for IDynamicParameters).
internal Identity ForGrid<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(Type primaryType, int gridIndex) =>
new Identity<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(sql, commandType, connectionString, primaryType, parametersType, gridIndex);
new Identity<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(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);

/// <summary>
/// Create an identity for use with DynamicParameters, internal use only.
Expand Down
67 changes: 67 additions & 0 deletions tests/Dapper.Tests/QueryMultipleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> identity.
// A subsequent Query<T> 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<int>().Single());
Assert.Equal(1, grid.Read<int>().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<int>(sql, second).Single());
}

// Regression for DapperLib/Dapper#243 and DapperLib/Dapper#2091:
// The reverse order has always worked (Query<T> 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<int>(sql, first).Single());

var second = new DynamicParameters();
second.Add("x", 99);
using var grid = connection.QueryMultiple(sql, second);
Assert.Equal(99, grid.Read<int>().Single());
Assert.Equal(99, grid.Read<int>().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 <int> seeds the shared identity
Assert.Equal(1, grid.Read<int>().Single());
Assert.Equal(1, grid.Read<int>().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<int>("#Repro2091", second, commandType: CommandType.StoredProcedure).Single());
}

[Fact]
public void TestMultiConversion()
{
Expand Down