首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

MFC CRecordset 獨立使用指南:無向導生成的靈活數據庫操作

     編輯:VC 教學

為什麼需要獨立使用 CRecordset?

AppWizard 生成的數據庫應用會自動創建 CRecordset 派生類並綁定特定表/視圖,雖然方便但缺乏靈活性:無法動態構建 SQL、難以跨表查詢、調試時綁定邏輯不透明。直接使用基類 CRecordset 可執行任意 SQL、動態綁定列、靈活控制游標類型,適合通用數據訪問層、報表引擎、動態查詢工具等場景。

環境準備


// stdafx.h 添加數據庫支持頭文件
#include  // MFC 數據庫類

// 確保項目設置:MFC 使用共享 DLL / 靜態庫均可
// 鏈接庫:自動鏈接 nafxcwd.lib / mfcsxxd.lib 等

核心類與關鍵函數

類/函數用途
CDatabase管理數據源連接(ODBC),執行 OpenEx/Open 連接 DSN 或無 DSN 連接字符串
CRecordset::Open執行 SQL 查詢/存儲過程,參數:游標類型、SQL 字符串、選項標誌
MoveFirst/Next/Prev/Last游標導航
IsBOF/IsEOF判斷游標邊界
GetFieldValue按索引/名稱獲取字段值,支持 CString/CDBVariant 等多種重載
GetODBCFieldInfo獲取字段元數據(類型、長度、精度等)

完整代碼示例

1. 連接數據庫


class CMyDBManager
{
public:
    CDatabase m_dbConn; // 成員變量保持連接存活

    BOOL Connect(LPCTSTR lpszDSN = _T("MyDSN"))
    {
        // 無 DSN 連接字符串示例:
        // "DRIVER={SQL Server};SERVER=localhost;DATABASE=TestDB;UID=sa;PWD=123;"
        BOOL bOK = m_dbConn.OpenEx(lpszDSN, CDatabase::noOdbcDialog);
        TRACE(_T("Connect %s: %d\n"), lpszDSN, bOK);
        return bOK;
    }

    void Disconnect()
    {
        if (m_dbConn.IsOpen())
            m_dbConn.Close();
    }
};

2. 執行查詢並遍歷結果集


void CMyDBManager::QuerySample()
{
    if (!m_dbConn.IsOpen()) return;

    CRecordset rs(&m_dbConn);
    
    // 參數說明:
    // CRecordset::forwardOnly - 僅前向游標,性能最佳,不支持 MovePrev
    // CRecordset::snapshot    - 快照型,只讀,支持雙向導航
    // CRecordset::dynaset     - 動態集,可更新,開銷最大
    CString strSQL = _T("SELECT loc_id, loc_name, created_dt FROM table1 ORDER BY loc_id");
    
    BOOL bOK = rs.Open(CRecordset::forwardOnly, strSQL, CRecordset::readOnly);
    TRACE(_T("Open result: %d, Fields: %d\n"), bOK, rs.GetODBCFieldCount());

    if (!bOK) return;

    CString strValue;
    while (!rs.IsEOF())
    {
        // 方式 1:按索引獲取 CString(自動類型轉換)
        rs.GetFieldValue((short)0, strValue); // loc_id
        TRACE(_T("ID: %s  "), strValue);
        
        rs.GetFieldValue((short)1, strValue); // loc_name
        TRACE(_T("Name: %s  "), strValue);
        
        rs.GetFieldValue((short)2, strValue); // created_dt
        TRACE(_T("Date: %s\n"), strValue);

        rs.MoveNext();
    }
    rs.Close();
}

3. 使用 CDBVariant 獲取任意類型數據


void CMyDBManager::QueryWithVariant()
{
    CRecordset rs(&m_dbConn);
    rs.Open(CRecordset::snapshot, _T("SELECT * FROM table1"), CRecordset::readOnly);

    CDBVariant var;
    while (!rs.IsEOF())
    {
        for (short i = 0; i < rs.GetODBCFieldCount(); ++i)
        {
            rs.GetFieldValue(i, var); // 統一獲取到 CDBVariant
            
            switch (var.m_dwType)
            {
            case DBVT_NULL:
                TRACE(_T("[NULL] "));
                break;
            case DBVT_BOOL:
                TRACE(_T("%s "), var.m_boolVal ? _T("True") : _T("False"));
                break;
            case DBVT_SHORT:
                TRACE(_T("%d "), var.m_iVal);
                break;
            case DBVT_LONG:
                TRACE(_T("%ld "), var.m_lVal);
                break;
            case DBVT_SINGLE:
                TRACE(_T("%.2f "), var.m_fltVal);
                break;
            case DBVT_DOUBLE:
                TRACE(_T("%.4f "), var.m_dblVal);
                break;
            case DBVT_DATE:
                {
                    CString str = var.m_pdate->Format(_T("%Y-%m-%d %H:%M:%S"));
                    TRACE(_T("%s "), str);
                }
                break;
            case DBVT_STRING:
            case DBVT_ASTRING:
                TRACE(_T("%s "), (LPCTSTR)*var.m_pstring);
                break;
            case DBVT_BINARY:
                TRACE(_T("[BINARY:%lu bytes] "), var.m_pbinary->GetSize());
                break;
            default:
                TRACE(_T("[TYPE:%lu] "), var.m_dwType);
            }
        }
        TRACE(_T("\n"));
        rs.MoveNext();
    }
    rs.Close();
}

4. 執行非查詢 SQL(INSERT/UPDATE/DELETE/DDL)


BOOL CMyDBManager::ExecuteSQL(LPCTSTR lpszSQL)
{
    if (!m_dbConn.IsOpen()) return FALSE;
    
    try
    {
        m_dbConn.ExecuteSQL(lpszSQL);
        return TRUE;
    }
    catch (CDBException* e)
    {
        TRACE(_T("SQL Error: %s\n"), e->m_strError);
        e->Delete();
        return FALSE;
    }
}

// 使用示例
ExecuteSQL(_T("INSERT INTO table1 (loc_name) VALUES ('New Location')"));
ExecuteSQL(_T("UPDATE table1 SET loc_name='Modified' WHERE loc_id=10"));
ExecuteSQL(_T("DELETE FROM table1 WHERE loc_id > 100"));

游標類型選擇指南

類型特點適用場景性能
forwardOnly單向、只讀、無緩存大數據集順序讀取、報表生成、數據導出★★★★★ 最快
snapshot靜態快照、雙向、只讀需要隨機訪問的只讀數據、表單綁定★★★☆☆
dynaset動態、可更新、雙向需編輯數據的表單、雙向導航★★☆☆☆ 開銷大
dynamic服務端游標、實時反映他人變更高併發協作環境(需驅動程序支持)★☆☆☆☆ 視驅動而定

常見問題與最佳實踐

  • 異常處理:所有數據庫操作包裝在 try-catch(CDBException*) 中,捕獲後調用 e->Delete() 釋放異常對象。
  • 資源釋放:CRecordset::Close() 必須調用,析構函數會自動調用但顯式關閉更安全。
  • 參數化查詢:避免 SQL 注入,使用 CRecordset::Open 參數綁定或 CDatabase::ExecuteSQL 參數化(需 ODBC 3.0+ 驅動支持)。
  • 大字段處理:CLongBinary / CByteArray 處理 BLOB/IMAGE/TEXT 字段,需設置 CRecordset::useMultiRowFetch 等選項優化。
  • 連接池:ODBC 驅動程序層面支持連接池,CDatabase 重複 Open/Close 可複用物理連接。
  • Unicode 支持:VC++ 2005+ 默認 Unicode 構建,CString 為 wchar_t,ODBC 驅動需支持 Unicode(SQL_WCHAR)。

擴展:封裝通用數據訪問類


class CDBAccessor
{
    CDatabase& m_db;
public:
    CDBAccessor(CDatabase& db) : m_db(db) {}

    // 返回 vector> 通用結果集
    std::vector> Query(LPCTSTR sql, int nCursorType = CRecordset::forwardOnly)
    {
        std::vector> results;
        CRecordset rs(&m_db);
        if (!rs.Open(nCursorType, sql, CRecordset::readOnly)) return results;

        int nFields = rs.GetODBCFieldCount();
        std::vector colNames(nFields);
        for (int i = 0; i < nFields; ++i)
        {
            ODBCFIELDINFO info;
            rs.GetODBCFieldInfo(i, info);
            colNames[i] = info.m_strName;
        }

        CDBVariant var;
        while (!rs.IsEOF())
        {
            std::map row;
            for (int i = 0; i < nFields; ++i)
            {
                rs.GetFieldValue(i, var);
                row[colNames[i]] = var; // 複製構造
            }
            results.push_back(std::move(row));
            rs.MoveNext();
        }
        rs.Close();
        return results;
    }
};

總結

獨立使用 CRecordset 基類打破了向導生成代碼的靈活性限制,配合 CDBVariant 實現類型無關的字段訪問,構建通用數據訪問層成為可能。掌握游標類型選擇、異常處理、資源管理等核心要點,可編寫健壯、高效、可維護的數據庫應用代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved