diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index 34f0848d9..c2c82de0f 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -228,14 +228,20 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) outputCallbacks?.ForEach(generator => generator()); } - foreach (var param in parameters.Values) - { - if (param.CameFromTemplate) continue; - - var dbType = param.DbType; - var val = param.Value; - string name = Clean(param.Name); - var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter; + foreach (var param in parameters.Values) + { + if (param.CameFromTemplate) continue; + + var dbType = param.DbType; + var val = param.Value; + string name = Clean(param.Name); + if (RemoveUnused && identity.CommandType.GetValueOrDefault(CommandType.Text) == CommandType.Text + && (!SqlMapper.Settings.SupportLegacyParameterTokens || !CompiledRegex.LegacyParameter.IsMatch(command.CommandText)) + && !SqlMapper.IsParameterUsed(command.CommandText, name)) + { + continue; + } + var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter; SqlMapper.ITypeHandler? handler = null; if (dbType is null && val is not null && !isCustomQueryParameter) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 8f3956cab..262c5d721 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2411,16 +2411,19 @@ public static object SanitizeParameterValue(object? value) return value; } - private static IEnumerable FilterParameters(IEnumerable parameters, string sql) - { - var list = new List(16); - foreach (var p in parameters) - { - if (Regex.IsMatch(sql, @"[?@:$]" + p.Name + @"([^\p{L}\p{N}_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)) - list.Add(p); - } - return list; - } + private static IEnumerable FilterParameters(IEnumerable parameters, string sql) + { + var list = new List(16); + foreach (var p in parameters) + { + if (IsParameterUsed(sql, p.Name)) + list.Add(p); + } + return list; + } + + internal static bool IsParameterUsed(string sql, string name) + => Regex.IsMatch(sql, @"[?@:$]" + Regex.Escape(name) + @"([^\p{L}\p{N}_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant); /// /// Replace all literal tokens with their text form. diff --git a/Directory.Packages.props b/Directory.Packages.props index 2f91a3cca..8f2772419 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -35,6 +35,7 @@ + diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index 93b498618..09947a89a 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -31,8 +31,9 @@ - - + + + diff --git a/tests/Dapper.Tests/FakeDbTests.Issue1241.cs b/tests/Dapper.Tests/FakeDbTests.Issue1241.cs new file mode 100644 index 000000000..aa0f329a0 --- /dev/null +++ b/tests/Dapper.Tests/FakeDbTests.Issue1241.cs @@ -0,0 +1,30 @@ +#if !NET481 +using System.Collections.Generic; +using System.Linq; +using pengdows.crud.fakeDb; +using Xunit; + +namespace Dapper.Tests +{ + public sealed class FakeDbIssue1241Tests + { + [Fact] + public void RemoveUnusedSkipsExplicitlyAddedCollectionParameters() + { + using var conn = new fakeDbConnection(new FakeDataStore()); + conn.EnqueueNonQueryResult(1); + conn.Open(); + + var parameters = new DynamicParameters(); + parameters.Add("ids", new[] { 1, 2, 3 }); + parameters.Add("used", 42); + + conn.Execute("select 1 where Value = @used", parameters); + + var command = conn.LastCreatedCommand!; + Assert.DoesNotContain(command.Parameters.Cast(), + p => p.ParameterName.StartsWith("ids", System.StringComparison.OrdinalIgnoreCase)); + } + } +} +#endif