tursodatabase/turso · error · InvalidOperationException
Missing parameter values for {parameters}.
Error message
Missing parameter values for {parameters}. What it means
Thrown when a prepared statement still has unbound SQL parameters after SqliteCommand bound everything in its Parameters collection. SQLite requires every placeholder (@name, $name, :name, or positional ?) in a statement to receive a value before execution, and Turso.Data.Sqlite enforces this at bind time (SqliteCommand.BindParameters). Note that extra parameters in the collection that match no placeholder are silently skipped, so a typo'd parameter name surfaces here as a 'missing' parameter, not as 'parameter not found'. The message names the unbound parameter, or its 1-based ordinal when the placeholder is positional (unnamed).
Source
Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteCommand.cs:671
if (string.IsNullOrEmpty(parameter.ParameterName))
throw new InvalidOperationException(Properties.Resources.RequiresSet(nameof(parameter.ParameterName)));
if (!parameter.HasValue)
throw new InvalidOperationException(Properties.Resources.RequiresSet(nameof(parameter.Value)));
var parameterIndex = FindParameterIndex(statement, parameter.ParameterName, parameterCount);
if (parameterIndex == 0)
continue;
TursoBindings.BindParameter(statement, parameterIndex, parameter.ToTursoValue());
boundParameters[parameterIndex] = true;
}
for (var i = 1; i <= parameterCount; i++)
{
if (!boundParameters[i])
{
var parameterName = TursoBindings.GetParameterName(statement, i);
throw new InvalidOperationException(
parameterName is null
? Properties.Resources.MissingParameters(i)
: Properties.Resources.MissingParameters(parameterName));
}
}
}
private static bool IsEmptyCommand(string commandText)
{
foreach (var line in commandText.Split('\n'))
{
var trimmedLine = line.Trim();
if (trimmedLine.Length != 0 && !trimmedLine.StartsWith("--", StringComparison.Ordinal))
return false;
}
return true;
}View on GitHub (pinned to 6c72522679)
Solutions
- Add a SqliteParameter for every placeholder in the SQL text, with a name matching exactly (prefix characters @/:/$ on the SqliteParameter are tolerated, the base name must match).
- If the SQL was built dynamically, log the final CommandText at the error site and diff its placeholders against cmd.Parameters to find the mismatch.
- For positional '?' placeholders, ensure the number of parameters added is at least the number of placeholders in the statement.
- Null out stale parameters (cmd.Parameters.Clear()) before reusing a cached SqliteCommand so leftovers do not mask the real set.
Example fix
// before
cmd.CommandText = "SELECT * FROM users WHERE id = @id AND name = @name";
cmd.Parameters.AddWithValue("id", 42);
cmd.ExecuteReader(); // throws: Missing parameter values for @name
// after
cmd.CommandText = "SELECT * FROM users WHERE id = @id AND name = @name";
cmd.Parameters.AddWithValue("id", 42);
cmd.Parameters.AddWithValue("name", "alice");
cmd.ExecuteReader(); Defensive patterns
Strategy: validation
Validate before calling
static readonly Regex ParamRegex = new(@"[@:$]\w+", RegexOptions.Compiled);
static void EnsureParametersBound(SqliteCommand cmd)
{
var declared = ParamRegex.Matches(cmd.CommandText)
.Select(m => m.Value.TrimStart('@', ':', '$'))
.Where(n => n.Length > 0)
.Distinct(StringComparer.Ordinal)
.ToHashSet(StringComparer.Ordinal);
var supplied = cmd.Parameters.Cast<SqliteParameter>()
.Select(p => p.ParameterName.TrimStart('@', ':', '$'))
.ToHashSet(StringComparer.Ordinal);
var missing = declared.Where(n => !supplied.Contains(n)).ToList();
if (declared.Count == 0 && cmd.Parameters.Count > 0)
return; // positional '?' SQL: ensure counts match instead
if (missing.Count > 0)
throw new InvalidOperationException($"Unbound SQL parameters: {string.Join(", ", missing)}");
}
// usage: EnsureParametersBound(cmd) before cmd.ExecuteReader() Prevention
- Derive parameter names from the same constant/format string used to build the SQL, never retype them.
- Call cmd.Parameters.Clear() before populating a reused command so stale entries cannot hide gaps.
- Run a debug assertion comparing CommandText placeholders against Parameters at dev time.
- Prefer named parameters over positional '?' so mismatches produce a name in the error, not an ordinal.
When it happens
Trigger: Executing 'SELECT * FROM t WHERE id = @id' without cmd.Parameters.Add(new SqliteParameter("id", value)); misspelling the parameter name relative to the SQL text (e.g. SQL uses @userId but code adds "uid"); dynamically building a WHERE clause with new placeholders while reusing a stale Parameters collection; using positional '?' placeholders where the statement declares more placeholders than parameters supplied; forgetting that each statement in a multi-statement batch contributes its own placeholders.
Common situations: Dynamic query builders (filter appended but parameter forgotten), copy-paste SQL with renamed placeholders, migrating code from providers that tolerate missing parameters by binding NULL, and ORM/query-helper bugs that cache a command but reset only part of its Parameters.
Related errors
- Parameter name {parameterName} is ambiguous.
- Encryption is not supported by {libraryName}.
- Parameter {parameter.ParameterName} was not found in the SQL
- Cannot access a disposed object. Object name: 'AggregateInv
- CollationRegistration
AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20).
Data as JSON: /api/errors/e33f53551ba53cdc.
Report an issue: GitHub.