SqliteDataReader 刚返回就关闭,检查 command 的释放时机
Microsoft.Data.Sqlite 的 command 释放会连带关闭活动 reader。函数里 using command 再返回 reader,调用方可能立即报 reader is closed;让资源所有权覆盖整个读取过程。
函数里创建 SqliteCommand,返回它的 reader;调用方刚执行 Read() 就失败:
Invalid attempt to call Read when reader is closed.
使用 Microsoft.Data.Sqlite 时,要检查函数退出前是否已释放 command。即使连接仍然打开、调用方还没主动关闭 reader,command 的释放也可能提前结束读取。
正式 Microsoft.Data.Sqlite 10.0.13 的 SqliteCommand.Dispose 会清理预备语句,清理过程中会先释放活动的 DataReader。这与某些其他 ADO.NET 驱动的行为不同,不能把另一种驱动上可用的资源生命周期直接照搬过来。
把 command 的存活期延长到读取结束
下面的写法会在函数返回时释放 command,因而把一个已经关闭的 reader 交给调用方:
SqliteDataReader OpenRows(SqliteConnection connection)
{
using var command = connection.CreateCommand();
command.CommandText = "SELECT 42";
return command.ExecuteReader();
}
这里的 using 会在 return 离开作用域时执行,并不会等到返回对象读完。把它改成 await using 也不能解决同样的所有权问题。
如果结果很小、业务本来只需要读取一个值,可以在拥有 command 的作用域内完成读取,再返回普通值。下面是假定连接已打开的函数示例:
long ReadAnswer(SqliteConnection connection)
{
using var command = connection.CreateCommand();
command.CommandText = "SELECT 42";
using var reader = command.ExecuteReader();
if (!reader.Read())
throw new InvalidOperationException("Expected one row.");
return reader.GetInt64(0);
}
示例需要 Microsoft.Data.Sqlite 与 System 命名空间。实际查询应保留参数化、事务和返回类型约定;不能为修复生命周期,把任意大结果集全部装进内存。
流式读取必须明确归谁释放
确实需要让调用方逐行消费时,由调用方在同一个作用域持有 connection、command 和 reader,读取结束后按 reader、command、connection 的顺序释放。若连接由更外层事务管理,仍由原来的事务所有者决定何时关闭它。
数据访问层也可以返回一个同时拥有这些资源的包装对象,但必须明确哪些资源归它管理,正常结束、提前停止和异常时都要释放。单独去掉 using、只返回 reader,又没有人接手 command 的释放,会把“过早关闭”换成资源泄漏。
通过迭代器封装时,资源应保留在真正执行枚举的作用域中,消费者还必须正确结束或释放枚举器。不要在创建枚举器的方法返回前先把底层 reader 关掉。
不要把版本计划当成现有保证
这条问题涉及 command 释放时连带关闭 reader 的具体行为。后续版本即使调整了该行为,连接关闭、显式释放 reader 和重新使用同一 command 仍可能终止读取,不能因此省略所有权设计。
当前若仍使用 10.0.13,应按已核实的生命周期组织代码,不依赖未来版本的处置方式。只有确认正式工件已经包含所需变更并完成兼容性检查,才把升级当作相应行为变化的依据。
复查时分别覆盖读完所有行、只读一行就退出、调用方异常三条路径。修正后 reader 应在业务需要的期间保持打开,结束后所有自有资源都应释放;连接未关闭不再被当作 reader 一定可读的证明。


