{"record":{"id":"903be635224ec264","repo":"tursodatabase/turso","slug":"sqlitecommand-tosqliteexception-ex-sql","errorCode":null,"errorMessage":"SqliteCommand.ToSqliteException(ex, sql)","messagePattern":"SqliteCommand\\.ToSqliteException\\(ex, sql\\)","errorType":"exception","errorClass":"SqliteException","httpStatus":null,"severity":"error","filePath":"bindings/dotnet/src/Turso.Data.Sqlite/SqliteTransaction.cs","lineNumber":341,"sourceCode":"    {\n        if (_managedTransaction is null)\n        {\n            Execute(sql);\n            return;\n        }\n\n        try\n        {\n            using var command = new global::Turso.TursoCommand(_connection!.ManagedConnection)\n            {\n                CommandText = sql,\n                Transaction = _managedTransaction,\n            };\n            command.ExecuteNonQuery();\n        }\n        catch (Turso.Raw.Public.TursoException ex)\n        {\n            throw SqliteCommand.ToSqliteException(ex, sql);\n        }\n    }\n\n    private async Task ExecuteTransactionCommandAsync(string sql, CancellationToken cancellationToken)\n    {\n        cancellationToken.ThrowIfCancellationRequested();\n        if (_managedTransaction is null)\n        {\n            Execute(sql);\n            return;\n        }\n\n        try\n        {\n            await using var command = new global::Turso.TursoCommand(_connection!.ManagedConnection)\n            {\n                CommandText = sql,\n                Transaction = _managedTransaction,","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/tursodatabase/turso/blob/6c7252267988c76e632af00a671e4b9788dfae13/bindings/dotnet/src/Turso.Data.Sqlite/SqliteTransaction.cs#L323-L359","documentation":"When a savepoint-control statement issued by SqliteTransaction (Save, Rollback, Release) fails at the engine level, the underlying Turso.Raw.Public.TursoException is converted via SqliteCommand.ToSqliteException(ex, sql) into a SqliteException carrying the failing SQL text. This wrapper exists so callers using the Microsoft.Data.Sqlite-compatible surface see a SqliteException instead of a raw Turso exception. The original native error code and message are preserved; the SQL statement is attached for diagnosis.","triggerScenarios":"Calling SqliteTransaction.Save(), SqliteTransaction.Rollback(), or SqliteTransaction.Release() on a transaction backed by a managed Turso transaction (_managedTransaction != null) where ExecuteNonQuery of the SAVEPOINT / ROLLBACK TO ... RELEASE statement raises TursoException - e.g. the connection dropped, the savepoint name is no longer valid, the transaction was already rolled back at the native layer, or an I/O error occurred while stepping the statement.","commonSituations":"Saving or releasing a savepoint after the underlying connection was closed or reset by the network/server; nesting savepoints incorrectly so a RELEASE references a name that was already released; concurrent access to the same connection from another thread causing the native transaction to be invalidated; database file locked or disk I/O failure during the statement step.","solutions":["Inspect SqliteException.SqliteErrorCode and Message to identify the underlying native error (busy, misuse, I/O) and address that root cause.","Verify the connection is open and the transaction has not already been completed before calling Save/Rollback/Release (ThrowIfCompleted guards the managed state, but the native side can still be stale).","Ensure savepoint/release calls are properly nested and each name is released only once on a single thread.","If the error is transient (busy/locked), retry the operation after the competing transaction completes or increase the busy timeout on the connection."],"exampleFix":"// before: releasing a savepoint without checking state\ntransaction.Save();\ndoWork();\ntransaction.Release();\n\n// after: guard state and handle transient failures\nif (transaction.Connection == null) throw new InvalidOperationException(\"transaction completed\");\ntransaction.Save();\ndoWork();\ntry { transaction.Release(); }\ncatch (SqliteException ex) when (ex.SqliteErrorCode == 5 /* SQLITE_BUSY */)\n{\n    // retry after the competing writer finishes\n}","handlingStrategy":"try-catch","validationCode":"if (transaction == null || transaction.Connection == null || transaction.Connection.State != ConnectionState.Open)\n    throw new InvalidOperationException(\"Cannot use a completed transaction's connection.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    transaction.Release();\n}\ncatch (SqliteException ex)\n{\n    // ex.SqliteErrorCode and ex.Message carry the wrapped native error and the failing SQL\n    if (ex.SqliteErrorCode == 5) { /* SQLITE_BUSY: retry */ }\n    else throw;\n}","preventionTips":["Always check transaction.Connection is not null before Save/Rollback/Release.","Keep savepoint usage strictly nested and single-threaded per connection.","Wrap transaction body in try/catch that rolls back on failure so later Release calls never hit an already-aborted transaction.","Set an adequate busy timeout when other writers may hold the database."],"tags":["dotnet","transaction","savepoint","sqlite-exception"],"backgroundTag":"sql-query-failed","analyzedSha":"6c7252267988c76e632af00a671e4b9788dfae13","analyzedAt":"2026-09-06T07:15:26.990Z","contentChangedAt":"2026-09-06T07:15:26.990Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}