You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用存储过程时MovePrev触发‘Fetch type out of range’错误的解决咨询

问题分析

高版本VS工具集(如v143)下,MFC的CRecordset调用存储过程后执行MovePrev()触发“Fetch type out of range”错误,本质是ODBC驱动/CRecordset实现的行为变更:旧版本(VS2010)会自动为存储过程返回的结果集创建可滚动游标,而高版本中,当使用executeDirect参数执行存储过程时,ODBC直接执行SQL语句,未创建支持反向滚动的游标,导致MovePrev()无法工作。

可行解决方案

方案1:调整CRecordset的Open参数(最小代码改动)

去掉CRecordset::executeDirect参数,让MFC自动处理游标创建:

rs.Open(CRecordset::snapshot, (LPCTSTR)"{CALL TestStoredProc}", CRecordset::readOnly | CRecordset::noDirtyFieldCheck);

原理:executeDirect会绕过MFC的游标管理逻辑,直接提交SQL给ODBC驱动。移除该参数后,MFC会根据指定的游标类型(这里是snapshot)请求ODBC创建可滚动游标,高版本驱动会正确响应。

方案2:显式指定游标类型并禁用Cursor Library(针对驱动兼容性问题)

如果方案1无效,尝试修改CDatabase的OpenEx参数和CRecordset的游标类型:

  1. 去掉CDatabase::useCursorLib,使用ODBC原生游标而非MFC的Cursor Library:
db.OpenEx((LPCTSTR)"DSN=ODBCName;UID=Login;PWD=Password", CDatabase::noOdbcDialog);
  1. 显式指定CRecordset的游标为可滚动快照:
// 在Open前设置
rs.m_nCursorType = CRecordset::snapshot;
rs.Open(CRecordset::snapshot, (LPCTSTR)"{CALL TestStoredProc}", CRecordset::readOnly | CRecordset::noDirtyFieldCheck);

原理:高版本ODBC驱动的原生游标对可滚动性支持更可靠,而旧的Cursor Library在高版本环境下可能存在兼容性问题。

方案3:修改存储过程,返回可滚动游标(适合需兼容多场景的情况)

如果上述客户端改动无法解决,可在存储过程中显式声明可滚动游标并返回结果:

create proc dbo.TestStoredProc
as
begin
    declare @rc1 int = 1
    declare @rc2 int = 2

    -- 声明可滚动静态游标
    declare test_cursor scroll cursor for
        select @rc1 union all select @rc2
    
    open test_cursor
    fetch next from test_cursor
    while @@fetch_status = 0
    begin
        fetch next from test_cursor
    end
    close test_cursor
    deallocate test_cursor
end
go

注意:此方案需修改大量存储过程,适合必须通过服务端控制游标的场景。

方案4:预读取全部结果到内存(兼容所有场景的兜底方案)

如果以上方案都不可行,可在Open后将所有结果读取到本地容器(如std::vector),后续操作直接操作内存数据,避免依赖ODBC游标:

void CTestDlg::OnBnClickedButton1()
{
    CDatabase db;
    db.OpenEx((LPCTSTR)"DSN=ODBCName;UID=Login;PWD=Password", CDatabase::useCursorLib | CDatabase::noOdbcDialog);
    CTestRecordset rs(&db);

    rs.Open(CRecordset::snapshot, (LPCTSTR)"{CALL TestStoredProc}", CRecordset::readOnly | CRecordset::executeDirect | CRecordset::noDirtyFieldCheck);

    // 预读取所有结果
    std::vector<int> results;
    do {
        results.push_back(rs.m_rc);
    } while (rs.MoveNext());

    // 重置到第一条
    rs.MoveFirst(); // 此时可正常操作,或直接用results容器

    CString t; 
    t.Format("第一条: %d", results[0]);
    ::AfxMessageBox(t); 

    t.Format("第二条: %d", results[1]);
    ::AfxMessageBox(t); 

    t.Format("回到第一条: %d", results[0]);
    ::AfxMessageBox(t); 

    rs.Close();
    db.Close();
}

原理:完全绕过ODBC游标的滚动限制,将数据本地化,适合无法修改存储过程或CRecordset参数的大型项目。

验证建议
  • 优先测试方案1(移除executeDirect),这是改动最小的方案,多数场景下可解决问题;
  • 若方案1无效,尝试方案2(调整Cursor Library和游标类型);
  • 前两个方案都失败时,再根据项目规模选择方案3或方案4。

内容的提问来源于stack exchange,提问作者Evgeny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 18:34:59