tursodatabase/turso · error · InvalidOperationException

Command was not prepared.

Error message

Command was not prepared.

What it means

After Prepare() returns on the local path, Execute() expects the compiled TursoStatementHandle in _statement (TursoCommand.cs:276-278). 'Command was not prepared' means that invariant broke: most plausibly a TursoCommand subclass overrode Prepare() as a no-op without calling base, or the connection's remote client appeared between Execute's IsRemote check and Prepare's early remote return, leaving _statement null.

Source

Thrown at bindings/dotnet/src/Turso.Data/TursoCommand.cs:287

            : value.Equals("ON", StringComparison.OrdinalIgnoreCase)
              || value.Equals("TRUE", StringComparison.OrdinalIgnoreCase)
              || value.Equals("YES", StringComparison.OrdinalIgnoreCase);
    }

    private DbDataReader Execute(CommandBehavior behavior = CommandBehavior.Default)
    {
        if (_connection is null)
            throw new InvalidOperationException("Connection must be set before executing a command.");

        if (_connection.IsRemote)
            return ExecuteRemoteAsync(behavior, CancellationToken.None).GetAwaiter().GetResult();

        IDisposable? syncOperation = _connection.EnterSyncOperation();
        try
        {
            PrepareCore();

            var statement = _statement ?? throw new InvalidOperationException("Command was not prepared.");
            _statement = null;
            try
            {
                var reader = new TursoDataReader(this, statement, behavior, syncOperation);
                syncOperation = null;
                return reader;
            }
            catch
            {
                statement.Dispose();
                throw;
            }
        }
        finally
        {
            syncOperation?.Dispose();
        }
    }

View on GitHub (pinned to c1e5928725)

Solutions

  1. Do not override Prepare(); if you must, always call base.Prepare() first.
  2. Use one command per thread and prefer creating commands per execution instead of sharing instances.
  3. Avoid opening/closing the connection concurrently with execution on the same objects.

Example fix

// before
public class MyCommand : TursoCommand
{
    public override void Prepare() { /* no base call */ }
}

// after
public class MyCommand : TursoCommand
{
    public override void Prepare() { base.Prepare(); /* extra work */ }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (cmd is not TursoCommand || cmd.Connection is not TursoConnection tc || tc.IsRemote)
    throw new InvalidOperationException("Local prepared execution requires a bound local TursoConnection.");

Try / catch

try
{
    using var reader = cmd.ExecuteReader();
}
catch (InvalidOperationException ex) when (ex.Message == "Command was not prepared.")
{
    // Invariant break: re-create the command from the connection and execute once more.
    using var fresh = conn.CreateCommand();
    fresh.CommandText = cmd.CommandText;
    using var reader2 = fresh.ExecuteReader();
}

Prevention

When it happens

Trigger: Subclassing TursoCommand and overriding Prepare() without base.Prepare(); racing Open/Close of the connection from another thread while a local Execute is in flight.

Common situations: Exotic customization of command behavior; multithreaded sharing of one command/connection pair; not produced by ordinary sequential use of the shipped API.

Related errors


AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-31). Data as JSON: /api/errors/ad85c2e99311ca69. Report an issue: GitHub.