{"record":{"id":"6c0139d7c6a74079","repo":"tursodatabase/turso","slug":"transaction-control-sql-is-not-supported-in-a-sqli","errorCode":null,"errorMessage":"Transaction-control SQL is not supported in a SqliteBatch. Use SqliteConnection.BeginTransaction and SqliteTransaction instead.","messagePattern":"Transaction-control SQL is not supported in a SqliteBatch\\. Use SqliteConnection\\.BeginTransaction and SqliteTransaction instead\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"bindings/dotnet/src/Turso.Data.Sqlite/SqliteBatch.cs","lineNumber":306,"sourceCode":"        };\n        var statements = new List<ManagedSqliteStatement>();\n        var managedCommands = new List<global::Turso.TursoBatchCommand>();\n        var mappings = new List<BatchCommandMapping>(_batchCommands.Count);\n        try\n        {\n            foreach (var batchCommand in _batchCommands.Items)\n            {\n                using var command = new SqliteCommand(batchCommand.CommandText, connection, _transaction);\n                foreach (SqliteParameter parameter in batchCommand.Parameters)\n                    command.Parameters.Add(parameter);\n                command.ValidateManagedParameterValues();\n\n                var commandStatements = ManagedSqliteStatementParser.Parse(batchCommand.CommandText);\n                if (commandStatements.Count == 0)\n                    throw new InvalidOperationException(\"Batch command text must contain a SQL statement.\");\n                if (commandStatements.Any(static statement => statement.IsTransactionControl))\n                {\n                    throw new InvalidOperationException(\n                        \"Transaction-control SQL is not supported in a SqliteBatch. \"\n                        + \"Use SqliteConnection.BeginTransaction and SqliteTransaction instead.\");\n                }\n                if (connection.IsReadOnly\n                    && commandStatements.Any(SqliteCommand.IsWriteStatement))\n                {\n                    throw new SqliteException(\n                        Properties.Resources.SqliteNativeError(8, \"attempt to write a readonly database\"),\n                        8);\n                }\n\n                var firstManagedIndex = managedCommands.Count;\n                foreach (var statement in commandStatements)\n                {\n                    string sql;\n                    if (preparing)\n                    {\n                        sql = SqliteCommand.RewriteFacadeStatement(statement.Sql, connection);","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/tursodatabase/turso/blob/6c7252267988c76e632af00a671e4b9788dfae13/bindings/dotnet/src/Turso.Data.Sqlite/SqliteBatch.cs#L288-L324","documentation":"SqliteBatch does not allow BEGIN/COMMIT/ROLLBACK (transaction-control statements) inside batch command text; transactions must be managed through SqliteConnection.BeginTransaction and SqliteTransaction so the batch executor can coordinate them. BuildBatch rejects any parsed statement flagged IsTransactionControl.","triggerScenarios":"Including 'BEGIN', 'BEGIN TRANSACTION', 'COMMIT', 'ROLLBACK', 'END', 'SAVEPOINT'/'RELEASE' handling flagged as transaction control in any SqliteBatchCommand.CommandText, then executing the batch.","commonSituations":"Porting a Microsoft.Data.Sqlite batch script that wrapped work in BEGIN/COMMIT; sharing SQL scripts between psql-style tooling and the batch API; code that appends COMMIT after DML.","solutions":["Remove BEGIN/COMMIT/ROLLBACK statements from the batch text and wrap execution in connection.BeginTransaction()/tx.Commit() instead","Assign batch.Transaction = (SqliteTransaction)tx so all statements run in that transaction","Keep transaction control in application code, not in batch SQL"],"exampleFix":"// before\nbatch.Commands.Add(new SqliteBatchCommand { CommandText = \"BEGIN; INSERT...; COMMIT;\" });\n// after\nusing var tx = connection.BeginTransaction();\nbatch.Transaction = tx;\nbatch.Commands.Add(new SqliteBatchCommand { CommandText = \"INSERT...\" });\nawait batch.ExecuteReaderAsync();\ntx.Commit();","handlingStrategy":"validation","validationCode":"var disallowed = new[]{\"BEGIN\",\"COMMIT\",\"ROLLBACK\",\"END\",\"SAVEPOINT\",\"RELEASE\"};\nbool hasTxControl = sql.Split(';').Any(s => disallowed.Contains(s.Trim().Split(' ')[0], StringComparer.OrdinalIgnoreCase));","typeGuard":"static bool IsTransactionControlSql(string sql) =>\n    sql.Split(';').Any(s => new[]{\"BEGIN\",\"COMMIT\",\"ROLLBACK\",\"END\",\"SAVEPOINT\",\"RELEASE\"}\n        .Contains(s.Trim().Split(' ').FirstOrDefault() ?? \"\", StringComparer.OrdinalIgnoreCase));","tryCatchPattern":"try { await batch.ExecuteReaderAsync(); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"Transaction-control\")) { /* rewrite SQL without BEGIN/COMMIT, use SqliteTransaction */ }","preventionTips":["Strip BEGIN/COMMIT/ROLLBACK from scripts destined for SqliteBatch","Use connection.BeginTransaction()/SqliteTransaction for all transaction scope","Keep two script variants: interactive (with txn control) and batch (without)"],"tags":["dotnet","batch","transaction","unsupported-sql"],"backgroundTag":"unsupported-operation","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"}