{"record":{"id":"7e914d3c088a221b","repo":"litedb-org/LiteDB","slug":"0-7e914d","errorCode":"0","errorMessage":"This thread contains an open cursors/query. Close cursors before Begin()","messagePattern":"This thread contains an open cursors/query\\. Close cursors before Begin\\(\\)","errorType":"exception","errorClass":"LiteException","httpStatus":null,"severity":"error","filePath":"LiteDB/Engine/Engine/Transaction.cs","lineNumber":23,"sourceCode":"using static LiteDB.Constants;\n\nnamespace LiteDB.Engine\n{\n    public partial class LiteEngine\n    {\n        /// <summary>\n        /// Initialize a new transaction. Transaction are created \"per-thread\". There is only one single transaction per thread.\n        /// Return true if transaction was created or false if current thread already in a transaction.\n        /// </summary>\n        public bool BeginTrans()\n        {\n            _state.Validate();\n\n            var transacion = _monitor.GetTransaction(true, false, out var isNew);\n\n            transacion.ExplicitTransaction = true;\n\n            if (transacion.OpenCursors.Count > 0) throw new LiteException(0, \"This thread contains an open cursors/query. Close cursors before Begin()\");\n\n            LOG(isNew, $\"begin trans\", \"COMMAND\");\n\n            return isNew;\n        }\n\n        /// <summary>\n        /// Persist all dirty pages into LOG file\n        /// </summary>\n        public bool Commit()\n        {\n            _state.Validate();\n\n            var transaction = _monitor.GetTransaction(false, false, out _);\n\n            if (transaction != null)\n            {\n                // do not accept explicit commit transaction when contains open cursors running","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/litedb-org/LiteDB/blob/f906a5f850678719e39a39a006cb66dcae563cfa/LiteDB/Engine/Engine/Transaction.cs#L5-L41","documentation":"Thrown by LiteEngine.BeginTrans() when the calling thread already has one or more open cursors (active query readers). LiteDB creates transactions per-thread and tracks all running cursors on that transaction; it refuses to enter an explicit transaction while a reader is still being iterated because the cursor's lifecycle is tied to the transaction it was created under. The guard prevents a deadlock-prone or inconsistent state where an explicit transaction boundary overlaps an in-flight cursor.","triggerScenarios":"Calling db.BeginTrans() while a previously opened IBsonDataReader (from db.Execute(string) or ILiteCollection.Query().ToEnumerable()) on the same thread has not yet been disposed. Happens when a developer iterates a reader lazily and calls BeginTrans() mid-iteration.","commonSituations":"Mixing manual transaction management with deferred query execution; using LINQ over a reader without disposing it first; foreach over a query result where the body calls BeginTrans().","solutions":["Dispose all open BsonDataReader/IBsonDataReader instances on the current thread before calling BeginTrans().","Materialize query results with ToList()/ToArray() before starting a transaction.","Wrap readers in using-statements so they close before BeginTrans() is reached.","Reconsider whether you need an explicit transaction at all — most single operations auto-create and commit their own transaction."],"exampleFix":"// before\nvar reader = db.Execute(\"SELECT $ FROM items\");\nreader.Read(); // cursor still open\ndb.BeginTrans(); // throws\n\n// after\nusing (var reader = db.Execute(\"SELECT $ FROM items\"))\n{\n    while (reader.Read()) { /* consume fully */ }\n}\ndb.BeginTrans();","handlingStrategy":"validation","validationCode":"// No public API exposes open cursor count; enforce discipline at the call site:\n// Ensure no reader is live before BeginTrans.\n// Track readers explicitly in a scope object.\npublic sealed class DbScope : IDisposable\n{\n    private readonly LiteDatabase _db;\n    private int _openReaders;\n    public DbScope(LiteDatabase db) => _db = db;\n    public IDisposable ReadScope()\n    {\n        _openReaders++;\n        return new Closer(() => _openReaders--);\n    }\n    public void BeginTransaction()\n    {\n        if (_openReaders > 0)\n            throw new InvalidOperationException($\"Cannot BeginTrans: {_openReaders} reader(s) open. Dispose them first.\");\n        _db.BeginTrans();\n    }\n    public void Dispose() => _db.Dispose();\n    private sealed class Closer : IDisposable\n    {\n        private readonly Action _onDispose;\n        public Closer(Action onDispose) => _onDispose = onDispose;\n        public void Dispose() => _onDispose();\n    }\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    db.BeginTrans();\n}\ncatch (LiteException ex) when (ex.Message.Contains(\"open cursors/query\"))\n{\n    // Dispose tracked readers, then retry once, or surface a clear error.\n    throw new InvalidOperationException(\"Close all open readers before starting a transaction.\", ex);\n}","preventionTips":["Always wrap BsonDataReader in a using-statement.","Materialize query results before calling BeginTrans().","Prefer auto-transactions for single operations to avoid manual lifecycle bugs.","Never iterate a reader lazily across a BeginTrans() boundary."],"tags":["transaction","cursor","concurrency","litedb-engine"],"backgroundTag":null,"analyzedSha":"f906a5f850678719e39a39a006cb66dcae563cfa","analyzedAt":"2026-08-13T21:56:30.148Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}