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() {