{"record":{"id":"8f4cbd04c8c3b24e","repo":"tursodatabase/turso","slug":"the-data-reader-is-closed","errorCode":null,"errorMessage":"The data reader is closed.","messagePattern":"The data reader is closed\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"bindings/dotnet/src/Turso.Data/TursoDataReader.cs","lineNumber":322,"sourceCode":"\n    private static string GetTypeName(TursoValueType valueType)\n    {\n        return valueType switch\n        {\n            TursoValueType.Empty => \"\",\n            TursoValueType.Null => \"NULL\",\n            TursoValueType.Integer => \"INTEGER\",\n            TursoValueType.Real => \"REAL\",\n            TursoValueType.Text => \"TEXT\",\n            TursoValueType.Blob => \"BLOB\",\n            _ => throw new InvalidEnumArgumentException(nameof(valueType))\n        };\n    }\n\n    private void EnsureOpen()\n    {\n        if (IsClosed)\n            throw new InvalidOperationException(\"The data reader is closed.\");\n    }\n\n    private void RunExternalIo()\n    {\n        _syncConnection?.RunExternalIo();\n    }\n\n    private void ValidateOrdinal(int ordinal)\n    {\n        ArgumentOutOfRangeException.ThrowIfNegative(ordinal);\n        if (ordinal >= FieldCount)\n            throw new IndexOutOfRangeException($\"column ordinal {ordinal} is out of range\");\n    }\n}\n\ninternal static class DataReaderCompatibility\n{\n    public static DataTable CreateSchemaTable(DbDataReader reader)","sourceCodeStart":304,"sourceCodeEnd":340,"githubUrl":"https://github.com/tursodatabase/turso/blob/6c7252267988c76e632af00a671e4b9788dfae13/bindings/dotnet/src/Turso.Data/TursoDataReader.cs#L304-L340","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nusing var reader = cmd.ExecuteReader();\nreturn MapRows(reader); // MapRows is lazy and enumerates after dispose\n\n// after\nusing var reader = cmd.ExecuteReader();\nvar rows = new List<Row>();\nwhile (reader.Read()) rows.Add(MapRow(reader));\nreturn rows;","handlingStrategy":"validation","validationCode":"if (reader is { IsClosed: false })\n    value = reader.GetString(ordinal);","typeGuard":null,"tryCatchPattern":"try { rows = MapAll(reader); } catch (InvalidOperationException ex) when (ex.Message == \"The data reader is closed.\") { /* lifetime bug: materialize before dispose */ }","preventionTips":["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."],"tags":["csharp","dotnet","data-reader","object-disposed","lifetime"],"backgroundTag":"object-disposed","analyzedSha":"6c7252267988c76e632af00a671e4b9788dfae13","analyzedAt":"2026-08-31T11:17:35.598Z","contentChangedAt":"2026-08-31T11:17:35.598Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}