调用存储过程时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的游标类型:
- 去掉
CDatabase::useCursorLib,使用ODBC原生游标而非MFC的Cursor Library:
db.OpenEx((LPCTSTR)"DSN=ODBCName;UID=Login;PWD=Password", CDatabase::noOdbcDialog);
- 显式指定
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

