tursodatabase/turso · error · InvalidOperationException

Parameter name {parameterName} is ambiguous.

Error message

Parameter name {parameterName} is ambiguous.

What it means

SqliteCommand.FindParameterIndex resolves a parameter supplied without its prefix character by trying @name, $name, and :name variants against the statement's declared placeholders. If the statement declares more than one prefixed variant of the same base name (e.g. both @id and $id), resolution cannot tell which one you meant and throws InvalidOperationException. This only happens when the SqliteParameter name itself is unprefixed; a prefixed parameter name matches exactly one placeholder and never conflicts.

Source

Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteCommand.cs:1084

        }

        return message;
    }

    private static int FindParameterIndex(TursoStatementHandle statement, string parameterName, int parameterCount)
    {
        var index = FindExactParameterIndex(statement, parameterName, parameterCount);
        if (index != 0 || IsPrefixed(parameterName))
            return index;

        foreach (var prefix in new[] { '@', '$', ':' })
        {
            var prefixedIndex = FindExactParameterIndex(statement, prefix + parameterName, parameterCount);
            if (prefixedIndex == 0)
                continue;

            if (index != 0)
                throw new InvalidOperationException(Properties.Resources.AmbiguousParameterName(parameterName));

            index = prefixedIndex;
        }

        return index;
    }

    private static int FindExactParameterIndex(TursoStatementHandle statement, string parameterName, int parameterCount)
    {
        for (var i = 1; i <= parameterCount; i++)
        {
            if (string.Equals(TursoBindings.GetParameterName(statement, i), parameterName, StringComparison.Ordinal))
                return i;
        }

        return 0;
    }

View on GitHub (pinned to 6c72522679)

Solutions

  1. Rewrite the SQL to use one consistent prefix character for every placeholder of the same base name.
  2. Or supply the fully prefixed name in the SqliteParameter (e.g. "@foo" or "$foo") so it binds by exact match and cannot be ambiguous.
  3. If both variants are genuinely needed, add two SqliteParameter objects, each with its exact prefixed name.

Example fix

// before
cmd.CommandText = "SELECT @foo + $foo";
cmd.Parameters.AddWithValue("foo", 1); // throws: Parameter name foo is ambiguous

// after
cmd.CommandText = "SELECT @foo + @foo2";
cmd.Parameters.AddWithValue("@foo", 1);
cmd.Parameters.AddWithValue("@foo2", 2);
Defensive patterns

Strategy: validation

Validate before calling

static readonly Regex PrefixParam = new(@"[@:$](\w+)", RegexOptions.Compiled);

static void EnsureNoAmbiguousParameters(string sql, string parameterName)
{
    if (parameterName.StartsWith('@') || parameterName.StartsWith('$') || parameterName.StartsWith(':'))
        return; // prefixed names bind exactly and are never ambiguous
    var base2 = parameterName.TrimStart('@', ':', '$');
    var variants = PrefixParam.Matches(sql)
        .Select(m => (prefix: m.Value[0], name: m.Groups[1].Value))
        .Where(p => p.name == base2)
        .Select(p => p.prefix)
        .Distinct()
        .ToList();
    if (variants.Count > 1)
        throw new InvalidOperationException($"SQL declares {parameterName} with multiple prefixes: {string.Join(", ", variants)}");
}

Type guard

static bool IsPrefixedParameterName(string name) => name.StartsWith('@') || name.StartsWith('$') || name.StartsWith(':');

// Prefer always-supplied prefixed names so binding is exact-match and cannot be ambiguous.

Try / catch

null

Prevention

When it happens

Trigger: SQL like 'SELECT @foo + $foo' while adding cmd.Parameters.AddWithValue("foo", 1); a statement mixing ':id' in one clause and '@id' in another (e.g. SQL assembled from fragments written by different authors) while binding "id"; test harnesses that concatenate prefix styles.

Common situations: Codebases that mix SQLite prefix conventions across files, generated SQL that injects parameters with a default prefix into hand-written SQL that uses a different one, and copy-paste between examples using @, :, and $.

Related errors


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20). Data as JSON: /api/errors/b7c626b1f0355abf. Report an issue: GitHub.