tursodatabase/turso · error · NullReferenceException

database is invalid

Error message

database is invalid

What it means

TursoDatabaseHandle wraps two native pointers (database and connection). ThrowIfInvalid throws NullReferenceException when either is zero, which happens when the handle was released (ReleaseHandle nulls both after DatabaseDeinit) or never fully initialized. It guards every TursoBindings entry point (PrepareStatement, RegisterScalarFunction, LoadExtension, and so on).

Source

Thrown at bindings/dotnet/src/Turso.Raw/Public/Handles/TursoDatabaseHandle.cs:40

        if (_database != IntPtr.Zero)
            TursoInterop.DatabaseDeinit(_database);

        if (_ownerReferenceAdded)
        {
            _owner!.DangerousRelease();
            _ownerReferenceAdded = false;
        }

        handle = IntPtr.Zero;
        _database = IntPtr.Zero;
        _owner = null;
        return true;
    }

    public void ThrowIfInvalid()
    {
        if (IsInvalid)
            throw new NullReferenceException("database is invalid");
    }

    public static TursoDatabaseHandle FromPtrs(IntPtr database, IntPtr connection)
    {
        var handle = new TursoDatabaseHandle();
        handle._database = database;
        handle.SetHandle(connection);
        return handle;
    }

    public static TursoDatabaseHandle FromConnectionPtr(IntPtr connection, SafeHandle owner)
    {
        ArgumentNullException.ThrowIfNull(owner);
        if (connection == IntPtr.Zero)
            throw new ArgumentException("Connection pointer must not be null.", nameof(connection));
        if (owner.IsInvalid || owner.IsClosed)
            throw new ObjectDisposedException(nameof(owner));

View on GitHub (pinned to c1e5928725)

Solutions

  1. Check handle.IsInvalid before each use and reopen when true.
  2. Scope ownership: create the handle with `using`/single owner, and have all consumers reference that owner instead of the raw handle.
  3. After reopening, re-prepare statements — they belong to the old connection.
  4. For encrypted databases, remember a failed Open due to a wrong key also leaves you without a usable handle; surface that error instead of reusing the handle.

Example fix

// before
db.Close();
var stmt = TursoBindings.PrepareStatement(db, sql); // NullReferenceException: database is invalid

// after
db.Close();
db = TursoBindings.OpenDatabase(path);
var stmt = TursoBindings.PrepareStatement(db, sql);
Defensive patterns

Strategy: type-guard

Validate before calling

// check before every use of the database handle
if (db is null || db.IsInvalid)
    db = TursoBindings.OpenDatabase(path);

Type guard

static bool IsUsable(TursoDatabaseHandle db) => db is not null && !db.IsInvalid;

Try / catch

try { TursoBindings.PrepareStatement(db, sql); }
catch (NullReferenceException) when (db.IsInvalid) { db = TursoBindings.OpenDatabase(path); /* then retry */ }

Prevention

When it happens

Trigger: Using a database handle after Close()/Dispose() finalized it; reusing a handle stored in a field after another path disposed it; a partially constructed handle because OpenDatabase failed mid-way; DI container disposing a shared database while requests still run.

Common situations: Use-after-dispose in scoped services; caches that outlive the database; shutdown paths racing in-flight statements; storing the handle statically and recreating the database on config reload without updating the reference.

Related errors


AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-20). Data as JSON: /api/errors/a994e42479563f58. Report an issue: GitHub.