SqliteDataReader.GetValues 只取前两列,却报数组越界
IDataRecord允许短缓冲区,Microsoft.Data.Sqlite受影响实现却要求足够容纳整行。按实际列数分配,或逐列读取所需前缀,保留NULL与真实错误。
一行有三列,只想把前两列放进 new object[2],SqliteDataReader.GetValues() 却抛出 IndexOutOfRangeException。相同代码在其他ADO.NET提供程序里可以工作,应检查Microsoft.Data.Sqlite的短缓冲区行为,不必先怀疑查询返回了多余列。
IDataRecord.GetValues 的契约允许目标数组短于结果列数:只复制放得下的前几列,并返回复制数量。Microsoft.Data.Sqlite 10.0.13的实现却仍检查整个行宽,短数组会被拒绝。
全行读取时,按FieldCount分配
若调用方本来就要整行数据,可以在reader已经定位到有效行后,创建 new object[reader.FieldCount] 再调用 GetValues。查询投影发生变化时,数组大小也随实际结果更新,不依赖旧的固定列数。
这会读取整行。若实际只需少量前缀列,或后面的列很大,应选择明确的逐列读取,不为避开短数组问题额外读取全部数据。
只取前缀列时逐列读取
下面的辅助方法保留“最多取前requested列”的约定,调用前reader必须已经成功 Read() 到当前行:
static object[] ReadLeadingColumns(
System.Data.Common.DbDataReader reader, int requested)
{
if (requested < 0)
throw new ArgumentOutOfRangeException(nameof(requested));
int count = Math.Min(requested, reader.FieldCount);
var values = new object[count];
for (int i = 0; i < count; i++)
values[i] = reader.GetValue(i);
return values;
}
该片段使用 System 中的标准类型与方法,不调用受影响的 GetValues。SQL NULL仍由 GetValue 返回 DBNull.Value,没有被改成C# null或空字符串;调用方应保留原来的NULL处理。
返回数组长度就是本次读取数量。requested大于列数时只返回实际列数,等于0则不读取列。若原接口要求固定长度、以返回数量区分有效元素,应在自己的适配层明确保持该契约,不能直接替换后让调用方继续遍历不存在的元素。
也不要捕获所有 IndexOutOfRangeException 后返回空数组。字段序号错误、reader状态错误或业务自己的索引越界,都需要保留真实失败,不能与已确认的短缓冲区兼容问题混在一起。
保留列顺序和生命周期
投影顺序就是数组下标的含义。改SQL前缀或添加列后,应重新核对消费方,不能只保证数组不越界。
这个问题也不同于command释放后reader一并关闭。逐列读取仍要求连接、command与reader的生命周期正确,不能返回一个已关闭的reader再期待辅助方法能修复。
在隔离环境覆盖短数组需求、整行、超长请求、零列请求、SQL NULL与真实读取错误。后续版本即使已登记修复目标,也应确认正式发行工件的行为;未发布预览里程碑不代表10.0.13已经修正。


