tursodatabase/turso · error · InvalidOperationException
{method} requires an open data reader.
Error message
{method} requires an open data reader. What it means
InvalidOperationException thrown by EnsureOpen when any reader member is called after the reader is closed. The message interpolates the calling member ('{method} requires an open data reader.'), with get_ prefixes stripped so it reads like 'Read requires an open data reader.' or 'GetInt32 requires an open data reader.'. Close/Dispose sets _isClosed, and every data/metadata accessor funnels through this guard.
Source
Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteDataReader.cs:720
FinishClose();
}
private void FinishClose()
{
if (_isClosed)
return;
_closeCallback();
if ((_behavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection)
_command.Connection?.Close();
_isClosed = true;
}
private void EnsureOpen([CallerMemberName] string operation = "")
{
if (IsClosed)
throw new InvalidOperationException(Properties.Resources.DataReaderClosed(NormalizeOperationName(operation)));
}
private static string NormalizeOperationName(string operation)
=> operation.StartsWith("get_", StringComparison.Ordinal)
? operation[4..]
: operation;
private TursoStatementHandle GetStatement()
{
if (_statement is null)
throw new InvalidOperationException(Properties.Resources.NoData);
return _statement;
}
private void ValidateOrdinal(int ordinal)
{
if (ordinal < 0 || ordinal >= FieldCount)View on GitHub (pinned to 244cde92a7)
Solutions
- Keep all reads inside the using scope that owns the reader, and materialize (ToList) before leaving it when data must escape.
- Guard public helper APIs with if (reader.IsClosed) throw ... to fail with your own clear message instead.
- Return disconnected data (records/DTOs) from data-access methods instead of the live reader.
Example fix
// before
SqliteDataReader ReadAll() {
using var reader = cmd.ExecuteReader();
return reader; // caller uses it after dispose
}
// after
List<Row> ReadAll() {
using var reader = cmd.ExecuteReader();
var rows = new List<Row>();
while (reader.Read()) rows.Add(new Row(reader.GetInt32(0)));
return rows; // data survives reader disposal
} Defensive patterns
Strategy: validation
Validate before calling
if (reader.IsClosed) throw new InvalidOperationException("Reader already closed; re-execute the query.");
var v = reader.GetInt32(0); Try / catch
try { Process(reader); }
catch (InvalidOperationException ex) when (ex.Message.Contains("requires an open data reader"))
{ /* re-run the query and retry processing once */ } Prevention
- Scope reader usage with using and never let the reader escape that scope.
- Materialize results (ToList) before returning from data-access methods.
- Check IsClosed in helper APIs that accept a reader.
When it happens
Trigger: Accessing reader.Read(), GetName(i), GetInt32(i), FieldCount, etc. after Close() or after leaving the using block; storing the reader beyond the command lifetime (e.g. returning it from a method that disposed the command); double-enumerating a deferred LINQ result over a closed reader.
Common situations: Wrapping the reader in a method-scoped using but returning data lazily (IEnumerable yield over a disposed reader); sharing a reader with async code that completes after disposal; helper methods receiving a reader whose owner already closed it.
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.
- 1
AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20).
Data as JSON: /api/errors/201cf075a54d12b4.
Report an issue: GitHub.