DynamicParameters.AddParameters(IDbCommand): identity-free self-apply

The full bag protocol (per-parameter DbType/direction/size, templates,
literal replacement, RemoveUnused) already lives in AddParameters(command,
identity), but the identity requirement makes it uncallable from outside:
Identity's constructor is internal, and identity.Sql is consumed on the
first line. This overload builds the identity from the command's own text
and type, which is what identity.Sql is in practice - letting external
tooling (for example build-time code generators, aka Dapper.AOT) apply a
bag without reimplementing the protocol.

Test covers the bag+literal composition against a closed connection (no
database needed).
This commit is contained in:
Marc Gravell
2026-08-18 17:11:09 +01:00
parent c0b2097129
commit 3838f0ece5
3 changed files with 39 additions and 1 deletions
+14
View File
@@ -147,6 +147,20 @@ namespace Dapper
AddParameters(command, identity);
}
/// <summary>
/// Add all the parameters needed to the command just before it executes, using the
/// command's own text and type for any query-specific handling (literal tokens, etc);
/// this allows external tooling (for example build-time code generators) to apply a
/// parameter bag without constructing an <see cref="SqlMapper.Identity"/>
/// </summary>
/// <param name="command">The raw command prior to execution</param>
public void AddParameters(IDbCommand command)
{
if (command is null) throw new ArgumentNullException(nameof(command));
AddParameters(command, new SqlMapper.Identity(command.CommandText, command.CommandType,
command.Connection!, null, GetType()));
}
/// <summary>
/// If true, the command-text is inspected and only values that are clearly used are included on the connection
/// </summary>
+2 -1
View File
@@ -1,3 +1,4 @@
#nullable enable
static Dapper.SqlMapper.Settings.PreferTypeHandlersForEnums.get -> bool
static Dapper.SqlMapper.Settings.PreferTypeHandlersForEnums.set -> void
static Dapper.SqlMapper.Settings.PreferTypeHandlersForEnums.set -> void
Dapper.DynamicParameters.AddParameters(System.Data.IDbCommand! command) -> void
+23
View File
@@ -928,6 +928,29 @@ namespace Dapper.Tests
Assert.Null(p.Get<int?>("@b"));
}
[Fact]
public void AddParameters_Command_AppliesBagAndLiterals()
{
// the identity-free overload: external tooling (e.g. build-time code generators)
// can apply a bag using the command's own text for query-specific handling
using var connection = GetClosedConnection();
using var command = connection.CreateCommand();
command.CommandText = "select @a + {=b}";
var args = new DynamicParameters();
args.Add("a", 1);
args.Add("b", 2);
args.AddParameters(command);
Assert.Equal("select @a + 2", command.CommandText); // literal replaced
// (note the literal member is still attached as a parameter too - unused, but
// that matches what the bag does under vanilla execution)
var p = Assert.IsAssignableFrom<IDbDataParameter>(command.Parameters[0]);
Assert.Equal("a", p.ParameterName);
Assert.Equal(1, p.Value);
Assert.Equal(2, command.Parameters.Count);
}
[Fact]
public void TestAppendingAnonClasses()
{