tursodatabase/turso · error · InvalidOperationException
Parallel transactions are not supported.
Error message
Parallel transactions are not supported.
What it means
BeginTransaction throws InvalidOperationException when the connection already exposes a non-null Transaction, because SQLite permits only one active transaction per connection. There is no nested-transaction support and no automatic commit of the previous one; the second BeginTransaction is rejected outright. Note that disposing a SqliteTransaction rolls it back, so a transaction whose Dispose was skipped also keeps this guard tripped.
Source
Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteConnection.cs:346
{
ArgumentNullException.ThrowIfNull(connection);
}
public new virtual SqliteTransaction BeginTransaction()
=> BeginTransaction(IsolationLevel.Unspecified);
public virtual SqliteTransaction BeginTransaction(bool deferred)
=> BeginTransaction(IsolationLevel.Unspecified, deferred);
public new virtual SqliteTransaction BeginTransaction(IsolationLevel isolationLevel)
=> BeginTransaction(isolationLevel, deferred: isolationLevel == IsolationLevel.ReadUncommitted);
public virtual SqliteTransaction BeginTransaction(IsolationLevel isolationLevel, bool deferred)
{
if (State != ConnectionState.Open)
throw new InvalidOperationException(Properties.Resources.CallRequiresOpenConnection(nameof(BeginTransaction)));
if (Transaction is not null)
throw new InvalidOperationException(Properties.Resources.ParallelTransactionsNotSupported);
Transaction = new SqliteTransaction(this, isolationLevel, deferred);
return Transaction;
}
public virtual void CreateCollation(string name, Comparison<string>? comparison)
{
RegisterCollation(name, comparison is null ? null : (left, right) => comparison(left, right));
}
public virtual void CreateCollation<T>(string name, T state, Func<T, string, string, int>? comparison)
{
RegisterCollation(name, comparison is null ? null : (left, right) => comparison(state, left, right));
}
public virtual void CreateFunction<TResult>(string name, Func<TResult>? function, bool isDeterministic = false)
{
RegisterScalarFunction(name, 0, isDeterministic, function is null ? null : _ => function());View on GitHub (pinned to 6c72522679)
Solutions
- Commit, roll back, or dispose the existing transaction before starting a new one ('await using' / 'using' on the SqliteTransaction guarantees this).
- Pass the existing transaction down (set cmd.Transaction = conn.Transaction) instead of starting a second one; or use savepoints ('SAVEPOINT sp1') for nested semantics on SQLite 3.31+.
- Restructure so exactly one layer owns transaction boundaries.
Example fix
// before
using var conn = new SqliteConnection(cs);
conn.Open();
var tx1 = conn.BeginTransaction();
var tx2 = conn.BeginTransaction(); // throws: Parallel transactions are not supported
// after
using var conn = new SqliteConnection(cs);
conn.Open();
using (var tx1 = conn.BeginTransaction())
{
// do work; reuse tx1 on all commands, or nest with savepoints:
conn.ExecuteNonQuery("SAVEPOINT sp1");
// ...
conn.ExecuteNonQuery("RELEASE sp1");
} Defensive patterns
Strategy: validation
Validate before calling
static SqliteTransaction? ExistingOrNew(SqliteConnection conn)
=> conn.Transaction ?? conn.BeginTransaction();
// always scope transactions so Dispose rolls back on error:
// using var tx = conn.BeginTransaction(); Try / catch
null
Prevention
- Always create transactions with 'using' so exceptions cannot leave them active.
- Keep transaction ownership in one layer; pass conn.Transaction to commands instead of opening a second one.
- Use SAVEPOINT/RELEASE for nesting semantics on a single connection.
When it happens
Trigger: Calling BeginTransaction twice without Commit/Rollback/Dispose in between; wrapping code that starts its own transaction inside an outer transactional block; helper methods that 'ensure a transaction' unconditionally; forgetting 'using' on the first transaction so it stays active after an exception.
Common situations: Unit-of-work patterns layered over repository methods that also transact, re-entrant service code, and error paths that skip transaction cleanup leaving Transaction set on a pooled/reused connection instance.
Related errors
- {method} requires an open connection.
- 5
- Encryption is not supported by {libraryName}.
- Missing parameter values for {parameters}.
- Parameter name {parameterName} is ambiguous.
AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20).
Data as JSON: /api/errors/259ce80413d1797b.
Report an issue: GitHub.