tursodatabase/turso · error · InvalidOperationException
The data reader is closed.
Error message
The data reader is closed.
What it means
TursoDataReader guards every data-access member with EnsureOpen(), which throws this InvalidOperationException once the reader is closed or disposed. It is the ADO.NET-standard 'object used after Close/Dispose' signal: the native statement handle behind the reader is gone, so reads are impossible.
Source
Thrown at bindings/dotnet/src/Turso.Data/TursoDataReader.cs:322
private static string GetTypeName(TursoValueType valueType)
{
return valueType switch
{
TursoValueType.Empty => "",
TursoValueType.Null => "NULL",
TursoValueType.Integer => "INTEGER",
TursoValueType.Real => "REAL",
TursoValueType.Text => "TEXT",
TursoValueType.Blob => "BLOB",
_ => throw new InvalidEnumArgumentException(nameof(valueType))
};
}
private void EnsureOpen()
{
if (IsClosed)
throw new InvalidOperationException("The data reader is closed.");
}
private void RunExternalIo()
{
_syncConnection?.RunExternalIo();
}
private void ValidateOrdinal(int ordinal)
{
ArgumentOutOfRangeException.ThrowIfNegative(ordinal);
if (ordinal >= FieldCount)
throw new IndexOutOfRangeException($"column ordinal {ordinal} is out of range");
}
}
internal static class DataReaderCompatibility
{
public static DataTable CreateSchemaTable(DbDataReader reader)View on GitHub (pinned to 6c72522679)
Solutions
- Materialize results while the reader is alive: call ToList() inside the using block before returning.
- Check 'reader.IsClosed' before touching the reader in long-lived or callback code.
- Keep the reader's lifetime nested inside the command's and the connection's lifetime.
Example fix
// before using var reader = cmd.ExecuteReader(); return MapRows(reader); // MapRows is lazy and enumerates after dispose // after using var reader = cmd.ExecuteReader(); var rows = new List<Row>(); while (reader.Read()) rows.Add(MapRow(reader)); return rows;
Defensive patterns
Strategy: validation
Validate before calling
if (reader is { IsClosed: false })
value = reader.GetString(ordinal); Try / catch
try { rows = MapAll(reader); } catch (InvalidOperationException ex) when (ex.Message == "The data reader is closed.") { /* lifetime bug: materialize before dispose */ } Prevention
- Never return lazy enumerables from a using-scoped reader; materialize with ToList().
- Treat 'The data reader is closed' as a code-lifetime bug, not a runtime condition to retry.
When it happens
Trigger: Calling any Get* method, Read(), GetOrdinal(), or typed accessors after Close()/Dispose(), after the owning connection was closed, after the 'using' block ended, or from a deferred IEnumerable/LINQ query that enumerates the reader after it has been disposed.
Common situations: Returning a lazy projection from a using block (e.g. 'return rows.Select(r => Map(r));' inside 'using var reader'), sharing a reader with async code that outlives its scope, event handlers firing after the reader was closed by error handling.
Related errors
- The data is NULL at ordinal {ordinal}.
- Column name {name} is ambiguous between {column1} and {colum
- Column {name} was not found.
- No data exists for the row/column.
- {method} requires an open data reader.
AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-31).
Data as JSON: /api/errors/8f4cbd04c8c3b24e.
Report an issue: GitHub.