DapperLib/Dapper · error · InvalidOperationException
When passing parameters by position, each parameter can only
Error message
When passing parameters by position, each parameter can only be referenced once
What it means
Thrown during pseudo-positional parameter rewriting when the same `?key?` token is consumed more than once in a single command. In pseudo-positional mode (used for providers like Snowflake, or when `Settings.PseudoPositional` is enabled) each positional placeholder maps to exactly one parameter and is consumed the first time it is matched; re-referencing it would produce an ambiguous/unsatisfiable binding. Dapper tracks consumed keys in a HashSet and rejects duplicates.
Source
Thrown at Dapper/SqlMapper.cs:1923
{
if (cmd.Parameters.Count == 0) return;
Dictionary<string, IDbDataParameter> parameters = new(StringComparer.Ordinal);
foreach (IDbDataParameter param in cmd.Parameters)
{
if (!string.IsNullOrEmpty(param.ParameterName)) parameters[param.ParameterName] = param;
}
var consumed = new HashSet<string>(StringComparer.Ordinal);
bool firstMatch = true;
int index = 0; // use this to spoof names; in most pseudo-positional cases, the name is ignored, however:
// for "snowflake", the name needs to be incremental i.e. "1", "2", "3"
cmd.CommandText = CompiledRegex.PseudoPositional.Replace(cmd.CommandText, match =>
{
string key = match.Groups[1].Value;
if (!consumed.Add(key))
{
throw new InvalidOperationException("When passing parameters by position, each parameter can only be referenced once");
}
else if (parameters.TryGetValue(key, out IDbDataParameter? param))
{
if (firstMatch)
{
firstMatch = false;
cmd.Parameters.Clear(); // only clear if we are pretty positive that we've found this pattern successfully
}
// if found, return the anonymous token "?"
if (Settings.UseIncrementalPseudoPositionalParameterNames)
{
param.ParameterName = (++index).ToString();
}
cmd.Parameters.Add(param);
parameters.Remove(key);
consumed.Add(key);
return "?";
}
View on GitHub (pinned to 72a54c475f)
Solutions
- Give each positional occurrence a unique key and supply a matching parameter for each, e.g. `where a = ?id1? or b = ?id2?`.
- If you need the same value in two places, pass two parameters with the same value but different names.
- Disable pseudo-positional mode if your provider supports true named parameters and reuse (`Settings.PseudoPositional`).
- Rewrite the predicate to reference the parameter once, e.g. `where ?id? in (a, b)` or use OR on a single comparison.
Example fix
// before (pseudo-positional, throws on second ?id?)
var sql = "select * from t where a = ?id? or b = ?id?";
// after
var sql = "select * from t where a = ?id1? or b = ?id2?";
var p = new { id1 = id, id2 = id }; Defensive patterns
Strategy: validation
Validate before calling
// Ensure each pseudo-positional token is unique in the SQL.
static IEnumerable<string> PseudoPositionalKeys(string sql) =>
System.Text.RegularExpressions.Regex.Matches(sql, @"\?([^?]+)\?").Cast<Match>().Select(m => m.Groups[1].Value);
var dups = PseudoPositionalKeys(sql).GroupBy(k => k).Where(g => g.Count() > 1);
if (dups.Any()) throw new InvalidOperationException("Duplicate pseudo-positional keys: " + string.Join(",", dups.Select(g => g.Key))); Try / catch
try { var rows = cnn.Query<T>(sql, p); }
catch (InvalidOperationException ex) when (ex.Message.Contains("each parameter can only be referenced once"))
{ /* rewrite SQL so each ?key? is unique, supply one param per occurrence */ } Prevention
- Use unique placeholder names per occurrence in pseudo-positional SQL.
- If you need the same value twice, pass it under multiple names.
- Disable pseudo-positional mode when the provider supports named-parameter reuse.
When it happens
Trigger: Writing SQL like `where a = ?id? or b = ?id?` while the command is being processed by the pseudo-positional replacer (CompiledRegex.PseudoPositional). The second `?id?` cannot be re-consumed. Also triggered when a single named parameter is intentionally reused but the provider/dialect is configured for positional substitution.
Common situations: Migrating from a provider that allowed named-parameter reuse (e.g. SQL Server `@id ... @id`) to Snowflake or another positional dialect; enabling pseudo-positional mode globally; templating SQL that repeats a placeholder.
Related errors
- The member {name} of type {type.FullName} cannot be used as
- An enumerable sequence of parameters (arrays, lists, etc) is
- The first item in a list-expansion cannot be null
- ValueTuple should not be used for parameters - the language-
- MultiExec is not supported by ExecuteReader
AI-assisted analysis of DapperLib/Dapper@72a54c475f (2026-08-13).
Data as JSON: /api/errors/261eeee75919a43f.
Report an issue: GitHub.