{"record":{"id":"201cf075a54d12b4","repo":"tursodatabase/turso","slug":"method-requires-an-open-data-reader","errorCode":null,"errorMessage":"{method} requires an open data reader.","messagePattern":"(.+?) requires an open data reader\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"bindings/dotnet/src/Turso.Data.Sqlite/SqliteDataReader.cs","lineNumber":720,"sourceCode":"        FinishClose();\n    }\n\n    private void FinishClose()\n    {\n        if (_isClosed)\n            return;\n\n        _closeCallback();\n        if ((_behavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection)\n            _command.Connection?.Close();\n\n        _isClosed = true;\n    }\n\n    private void EnsureOpen([CallerMemberName] string operation = \"\")\n    {\n        if (IsClosed)\n            throw new InvalidOperationException(Properties.Resources.DataReaderClosed(NormalizeOperationName(operation)));\n    }\n\n    private static string NormalizeOperationName(string operation)\n        => operation.StartsWith(\"get_\", StringComparison.Ordinal)\n            ? operation[4..]\n            : operation;\n\n    private TursoStatementHandle GetStatement()\n    {\n        if (_statement is null)\n            throw new InvalidOperationException(Properties.Resources.NoData);\n\n        return _statement;\n    }\n\n    private void ValidateOrdinal(int ordinal)\n    {\n        if (ordinal < 0 || ordinal >= FieldCount)","sourceCodeStart":702,"sourceCodeEnd":738,"githubUrl":"https://github.com/tursodatabase/turso/blob/244cde92a7df7f9b8b8b7a4075c35a12977e303e/bindings/dotnet/src/Turso.Data.Sqlite/SqliteDataReader.cs#L702-L738","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nSqliteDataReader ReadAll() {\n    using var reader = cmd.ExecuteReader();\n    return reader; // caller uses it after dispose\n}\n\n// after\nList<Row> ReadAll() {\n    using var reader = cmd.ExecuteReader();\n    var rows = new List<Row>();\n    while (reader.Read()) rows.Add(new Row(reader.GetInt32(0)));\n    return rows; // data survives reader disposal\n}","handlingStrategy":"validation","validationCode":"if (reader.IsClosed) throw new InvalidOperationException(\"Reader already closed; re-execute the query.\");\nvar v = reader.GetInt32(0);","typeGuard":null,"tryCatchPattern":"try { Process(reader); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"requires an open data reader\"))\n{ /* re-run the query and retry processing once */ }","preventionTips":["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."],"tags":["csharp","dotnet","sqlite","data-reader","lifecycle"],"backgroundTag":"data-reader-closed","analyzedSha":"244cde92a7df7f9b8b8b7a4075c35a12977e303e","analyzedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}