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

  1. Give each positional occurrence a unique key and supply a matching parameter for each, e.g. `where a = ?id1? or b = ?id2?`.
  2. If you need the same value in two places, pass two parameters with the same value but different names.
  3. Disable pseudo-positional mode if your provider supports true named parameters and reuse (`Settings.PseudoPositional`).
  4. 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

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


AI-assisted analysis of DapperLib/Dapper@72a54c475f (2026-08-13). Data as JSON: /api/errors/261eeee75919a43f. Report an issue: GitHub.