tursodatabase/turso · error · InvalidOperationException
Execute requires the command to have a transaction when the
Error message
Execute requires the command to have a transaction when the connection has a pending local transaction.
What it means
If the SqliteConnection has a pending local transaction but the command's Transaction is null (and the text is not transaction control like BEGIN/COMMIT), EnsureExecutable throws InvalidOperationException. The provider will not guess which transaction a statement joins: auto-enlisting could commit your writes into a scope you meant to keep separate, so explicit enlistment is mandatory.
Source
Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteCommand.cs:599
private void EnsureExecutable(string method)
{
if (_hasOpenReader)
throw new InvalidOperationException(Properties.Resources.DataReaderOpen);
if (Connection is null || Connection.State != ConnectionState.Open)
throw new InvalidOperationException(Properties.Resources.CallRequiresOpenConnection(method));
if (Transaction is { IsCompleted: true } or { WasRolledBackExternally: true })
throw new InvalidOperationException(Properties.Resources.TransactionCompleted);
if (Transaction is not null && !ReferenceEquals(Transaction.Connection, Connection))
throw new InvalidOperationException(Properties.Resources.TransactionConnectionMismatch);
var connectionTransaction = Connection.Transaction;
if (connectionTransaction is null || ReferenceEquals(Transaction, connectionTransaction))
return;
if (connectionTransaction.IsCompleted)
throw new InvalidOperationException(Properties.Resources.TransactionCompleted);
if (!IsTransactionControlCommand(CommandText))
throw new InvalidOperationException(Properties.Resources.TransactionRequired);
}
private void CloseReader()
{
_hasOpenReader = false;
Connection?.ReaderClosed();
}
internal Action OwnBufferedReader()
{
_hasOpenReader = true;
Connection?.ReaderOpened();
return () =>
{
CloseReader();
Dispose();
};
}View on GitHub (pinned to 6c72522679)
Solutions
- Assign cmd.Transaction = tx (the transaction you began) before executing inside a transactional scope.
- Or commit/roll back the pending connection transaction before running unenlisted commands.
- Centralize command creation so ambient-transaction enlistment is automatic.
Example fix
// before using var tx = conn.BeginTransaction(); using var cmd = conn.CreateCommand(); cmd.CommandText = "UPDATE files SET seen = 1"; cmd.ExecuteNonQuery(); // throws: connection has a pending local transaction // after using var tx = conn.BeginTransaction(); using var cmd = conn.CreateCommand(); cmd.Transaction = tx; cmd.CommandText = "UPDATE files SET seen = 1"; cmd.ExecuteNonQuery(); tx.Commit();
Defensive patterns
Strategy: validation
Validate before calling
// keep the transaction you began; enlist every command created on that connection SqliteTransaction tx = conn.BeginTransaction(); var cmd = conn.CreateCommand(); cmd.Transaction ??= tx; // never leave a command unenlisted while a tx is pending
Try / catch
catch (InvalidOperationException ex) when (ex.Message.Contains("pending local transaction")) {
cmd.Transaction = tx; // enlist the tracked transaction, then retry once
cmd.ExecuteNonQuery();
} Prevention
- Assign Transaction on every command created after BeginTransaction.
- Commit or roll back pending transactions before running helper SQL.
- Wrap connection + transaction + commands in one scope object.
When it happens
Trigger: conn.BeginTransaction() followed by creating a new SqliteCommand without setting .Transaction and executing it; commands built by helpers that do not know about the ambient transaction; executing raw SQL while a service-layer transaction wraps the connection.
Common situations: Repository methods managing their own commands while an outer service layer opens the transaction; refactors that added BeginTransaction without enlisting existing commands; ORM/raw-SQL mixes.
Related errors
- Transaction must be a TursoTransaction.
- Transaction must be a SqliteTransaction.
- The transaction connection does not match the command connec
- The transaction has completed.
- Turso database is closed.
AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20).
Data as JSON: /api/errors/7f77c0245730017f.
Report an issue: GitHub.