diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs
index 1feb4d020..34f0848d9 100644
--- a/Dapper/DynamicParameters.cs
+++ b/Dapper/DynamicParameters.cs
@@ -147,6 +147,22 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id
AddParameters(command, identity);
}
+ ///
+ /// 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
+ ///
+ /// The raw command prior to execution
+ public void AddParameters(IDbCommand command)
+ {
+ if (command is null) throw new ArgumentNullException(nameof(command));
+ // dispatch via the interface so that a subclass which hides AddParameters and
+ // re-implements IDynamicParameters (an established extension pattern) still runs
+ ((SqlMapper.IDynamicParameters)this).AddParameters(command, new SqlMapper.Identity(
+ command.CommandText, command.CommandType, command.Connection!, null, GetType()));
+ }
+
///
/// If true, the command-text is inspected and only values that are clearly used are included on the connection
///
diff --git a/Dapper/PublicAPI.Unshipped.txt b/Dapper/PublicAPI.Unshipped.txt
index cf9343ccf..a4e055ca2 100644
--- a/Dapper/PublicAPI.Unshipped.txt
+++ b/Dapper/PublicAPI.Unshipped.txt
@@ -1,3 +1,4 @@
#nullable enable
static Dapper.SqlMapper.Settings.PreferTypeHandlersForEnums.get -> bool
-static Dapper.SqlMapper.Settings.PreferTypeHandlersForEnums.set -> void
\ No newline at end of file
+static Dapper.SqlMapper.Settings.PreferTypeHandlersForEnums.set -> void
+Dapper.DynamicParameters.AddParameters(System.Data.IDbCommand! command) -> void
diff --git a/tests/Dapper.Tests/ParameterTests.cs b/tests/Dapper.Tests/ParameterTests.cs
index 5eb455c65..cdad756ef 100644
--- a/tests/Dapper.Tests/ParameterTests.cs
+++ b/tests/Dapper.Tests/ParameterTests.cs
@@ -928,6 +928,55 @@ public void TestDynamicParamNullSupport()
Assert.Null(p.Get("@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(command.Parameters[0]);
+ Assert.Equal("a", p.ParameterName);
+ Assert.Equal(1, p.Value);
+ Assert.Equal(2, command.Parameters.Count);
+ }
+
+ [Fact]
+ public void AddParameters_Command_DispatchesViaInterface()
+ {
+ // a subclass that hides AddParameters and re-implements IDynamicParameters
+ // (the DynamicParameterWithIntTVP pattern) must still get its version called
+ using var connection = GetClosedConnection();
+ using var command = connection.CreateCommand();
+ command.CommandText = "select @a";
+
+ var args = new HidingBag();
+ args.Add("a", 1);
+ args.AddParameters(command);
+
+ Assert.True(args.CustomRan);
+ }
+
+ private class HidingBag : DynamicParameters, SqlMapper.IDynamicParameters
+ {
+ public bool CustomRan { get; private set; }
+ public new void AddParameters(IDbCommand command, SqlMapper.Identity identity)
+ {
+ base.AddParameters(command, identity);
+ CustomRan = true;
+ }
+ }
+
[Fact]
public void TestAppendingAnonClasses()
{