litedb-org/LiteDB · error · LiteException

0

0

Error message

This thread contains an open cursors/query. Close cursors before Begin()

What it means

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.

Source

Thrown at LiteDB/Engine/Engine/Transaction.cs:23

using static LiteDB.Constants;

namespace LiteDB.Engine
{
    public partial class LiteEngine
    {
        /// <summary>
        /// Initialize a new transaction. Transaction are created "per-thread". There is only one single transaction per thread.
        /// Return true if transaction was created or false if current thread already in a transaction.
        /// </summary>
        public bool BeginTrans()
        {
            _state.Validate();

            var transacion = _monitor.GetTransaction(true, false, out var isNew);

            transacion.ExplicitTransaction = true;

            if (transacion.OpenCursors.Count > 0) throw new LiteException(0, "This thread contains an open cursors/query. Close cursors before Begin()");

            LOG(isNew, $"begin trans", "COMMAND");

            return isNew;
        }

        /// <summary>
        /// Persist all dirty pages into LOG file
        /// </summary>
        public bool Commit()
        {
            _state.Validate();

            var transaction = _monitor.GetTransaction(false, false, out _);

            if (transaction != null)
            {
                // do not accept explicit commit transaction when contains open cursors running

View on GitHub (pinned to f906a5f850)

Solutions

  1. Dispose all open BsonDataReader/IBsonDataReader instances on the current thread before calling BeginTrans().
  2. Materialize query results with ToList()/ToArray() before starting a transaction.
  3. Wrap readers in using-statements so they close before BeginTrans() is reached.
  4. Reconsider whether you need an explicit transaction at all — most single operations auto-create and commit their own transaction.

Example fix

// before
var reader = db.Execute("SELECT $ FROM items");
reader.Read(); // cursor still open
db.BeginTrans(); // throws

// after
using (var reader = db.Execute("SELECT $ FROM items"))
{
    while (reader.Read()) { /* consume fully */ }
}
db.BeginTrans();
Defensive patterns

Strategy: validation

Validate before calling

// No public API exposes open cursor count; enforce discipline at the call site:
// Ensure no reader is live before BeginTrans.
// Track readers explicitly in a scope object.
public sealed class DbScope : IDisposable
{
    private readonly LiteDatabase _db;
    private int _openReaders;
    public DbScope(LiteDatabase db) => _db = db;
    public IDisposable ReadScope()
    {
        _openReaders++;
        return new Closer(() => _openReaders--);
    }
    public void BeginTransaction()
    {
        if (_openReaders > 0)
            throw new InvalidOperationException($"Cannot BeginTrans: {_openReaders} reader(s) open. Dispose them first.");
        _db.BeginTrans();
    }
    public void Dispose() => _db.Dispose();
    private sealed class Closer : IDisposable
    {
        private readonly Action _onDispose;
        public Closer(Action onDispose) => _onDispose = onDispose;
        public void Dispose() => _onDispose();
    }
}

Try / catch

try
{
    db.BeginTrans();
}
catch (LiteException ex) when (ex.Message.Contains("open cursors/query"))
{
    // Dispose tracked readers, then retry once, or surface a clear error.
    throw new InvalidOperationException("Close all open readers before starting a transaction.", ex);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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().

Related errors


AI-assisted analysis of litedb-org/LiteDB@f906a5f850 (2026-08-13). Data as JSON: /api/errors/7e914d3c088a221b. Report an issue: GitHub.