{"record":{"id":"dae08c0bba85b881","repo":"clockworklabs/SpacetimeDB","slug":"transaction-context-was-not-initialised","errorCode":null,"errorMessage":"Transaction context was not initialised.","messagePattern":"Transaction context was not initialised\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"crates/bindings-csharp/Runtime/TransactionalContextState.cs","lineNumber":51,"sourceCode":"\n    private Internal.TxContext? txContext;\n    private TTxContext? cachedUserTxContext;\n\n    public Internal.TxContext EnterTxContext(long timestampMicros)\n    {\n        var timestamp = new Timestamp(timestampMicros);\n        Timestamp = timestamp;\n        txContext = txContext?.WithTimestamp(timestamp) ?? createInitialTxContext(timestamp);\n        return txContext;\n    }\n\n    public void ExitTxContext() => txContext = null;\n\n    public TTxContext RequireTxContext()\n    {\n        var inner =\n            txContext\n            ?? throw new InvalidOperationException(\"Transaction context was not initialised.\");\n        cachedUserTxContext ??= createTxContext(inner);\n        cachedUserTxContext.Refresh(inner);\n        return cachedUserTxContext;\n    }\n\n    public TResult WithTx<TResult>(Func<TTxContext, TResult> body) =>\n        TryWithTx(tx => Result<TResult, Exception>.Ok(body(tx))).UnwrapOrThrow();\n\n    public TxOutcomeCore<TResult> TryWithTx<TResult, TError>(\n        Func<TTxContext, Result<TResult, TError>> body\n    )\n        where TError : Exception\n    {\n        try\n        {\n            var result = RunWithRetry(body);\n\n            return result switch","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/6dee26c6efc2856793e12b148a59742964f5d783/crates/bindings-csharp/Runtime/TransactionalContextState.cs#L33-L69","documentation":"The module runtime keeps per-invocation transaction state (TransactionalContextState): EnterTxContext opens it (called when the host starts a reducer/procedure transaction) and ExitTxContext clears it. RequireTxContext is what generated table/DbContext accessors call to obtain the user-facing transaction context; if txContext is null — i.e. no transaction is active on this thread — it throws this InvalidOperationException. It means module code touched the database outside a host-driven transaction.","triggerScenarios":"Calling table accessors (generated Db handles, Iter/Insert/Delete) from module static constructors, static field initializers, or arbitrary methods invoked outside a reducer/procedure callback; running DB logic in an HTTP handler body without going through WithTx/TryWithTx; calling reducer helper code from a background thread or timer.","commonSituations":"Seeding data at module startup from a constructor instead of a lifecycle reducer; refactoring reducer bodies into shared helpers that are then also called from non-transactional contexts; new HTTP handler features that read tables directly; unit tests invoking generated Db methods without the host harness.","solutions":["Move all table access inside [Reducer]/[Procedure] methods so the host enters the transaction first","For HTTP handlers and other non-transactional entry points, wrap DB work in WithTx(...) / TryWithTx(...) which manage EnterTxContext/ExitTxContext","Delete seeding/static-init code that touches tables and replace it with an init reducer the host invokes","Never cache or share Db handles across invocations — always resolve them inside the transactional callback"],"exampleFix":"// before - table touched outside any transaction\npublic static class Module {\n    static Module() { MyTable.Insert(new Row(1)); } // RequireTxContext throws\n}\n\n// after - seed from a lifecycle reducer (or wrap in WithTx)\n[SpacetimeDB.Reducer]\npublic static void Init(ReducerContext ctx) { MyTable.Insert(new Row(1)); }","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { DoTableWork(); }\ncatch (InvalidOperationException ioe) when (ioe.Message == \"Transaction context was not initialised.\")\n{\n    // code reached DB access outside a host transaction; move it into a reducer/procedure\n    // or wrap with WithTx/TryWithTx rather than swallowing it\n}","preventionTips":["Touch tables only inside [Reducer]/[Procedure] bodies or WithTx/TryWithTx callbacks","No table access in static constructors, field initializers, or background threads","Resolve Db/table handles per-invocation; never cache them across transactions","In tests, drive DB code through the host harness rather than calling generated accessors directly"],"tags":["csharp","transaction","reducer","spacetimedb-module","lifecycle"],"backgroundTag":"operation-outside-transaction","analyzedSha":"6dee26c6efc2856793e12b148a59742964f5d783","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}