DEBUG.WIKI
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →

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已经修正。