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

學生考試管理系統 ADO 版:從需求分析到 MDI 架構實作全紀錄

     編輯:關於VC++

專案背景與架構決策

作者從 Delphi 轉戰 VC++,參考 Carlos Antollini 的 ADO 封裝類 (v1.2) 並衍生出 CADOStorage,結合「一表一類」與 MDI 介面風格,打造一套學生考試管理系統。本文完整記錄資料庫設計、類結構、動態介面生成與考試流程編碼三大核心環節。

一、資料庫關係模型 (Access)

資料庫關係圖

  • 核心實體:年級、班級、學生、課程、考試、成績
  • 採用 E-R 模型正規化,外鍵關係明確
  • 擴充 表說明 中繼資料表:新增 外鍵個數、字段n、外鍵n、外部表n、顯示字段n (單表最多 4 個外鍵)

二、類結構與關聯關係

類關聯圖

使用 Rose 正向/逆向工程產出,涵蓋:

  • CBaseTbl:基礎表通用操作類,聚合 CADOStorage
  • CFieldRecord:欄位資訊結構體,含外鍵鏈表指標
  • 業務實體類:CExam、CExamClass、CExamStudent、CExamSubject,各自聚合 CADOStorage

三、動態介面生成機制:核心創新

1. 欄位資訊結構體

struct CFieldRecord {
    char FieldName[20];      // 欄位名(外鍵時為顯示欄位)
    char Value[255];         // 值
    bool IsBool;             // 是否布林
    bool IsStrType;          // 是否字串
    bool IsVisible;          // 是否顯示
    char DisplayName[20];    // 對應 ID 值(外鍵用)
    char FKtbl[20];          // 外鍵表名
    CFieldRecord* pFK;       // 外鍵資訊鏈表
};

2. 基礎表類核心方法

class CBaseTbl : public CObject {
public:
    bool GetFieldRecord(CPtrArray& FieldArray, CString ctblname, CString constr="");
    void AddExtraFieldRecord(CPtrArray& FieldArray, CString ctblname); // 掛載外鍵
    bool SelectUnionTbl(CString StrTblname, CString& Realtbl, bool ByRealTblName=false);
    void RefreshList(CListCtrl& List1);
    void ReQuery(CString TblName);
    void ExecSql(CString SqlStr);
protected:
    CADOStorage m_Storage;
private:
    CString m_TblName;
};

3. 從資料庫載入欄位與外鍵資訊

bool CBaseTbl::GetFieldRecord(CPtrArray& FieldArray, CString ctblname, CString constr) {
    // 1. 清理舊資料、判斷記錄位置(IsZeroRecord)
    int count = m_Storage.GetFieldCount();
    for (int i=0; i<count; i++) {
        CFieldRecord* fldrec = new CFieldRecord();
        CString fldvalue;
        if (IsZeroRecord) fldvalue = _T("");
        else m_Storage.GetFieldValue(i, fldvalue);
        
        CADOFieldInfo fldinfo;
        m_Storage.GetFieldInfo(i, &fldinfo);
        strcpy(fldrec->FKtbl, ctblname);
        strcpy(fldrec->FieldName, fldinfo.m_strName);
        strcpy(fldrec->Value, fldvalue);
        
        // 型別判斷
        switch (fldinfo.m_nType) {
            case VT_DATE: fldrec->IsStrType=true; fldrec->IsBool=false; break;
            // ... 其他型別
            default: fldrec->IsStrType=false; fldrec->IsBool=false;
        }
        fldrec->pFK = NULL;
        fldrec->IsVisible = true;
        FieldArray.Add(fldrec);
    }
    // 2. 遞迴掛載外鍵資訊
    AddExtraFieldRecord(FieldArray, ctblname);
    return true;
}

4. 動態建立編輯介面 (對話框)

// CBMDialog::OnInitDialog 片段
for (int i=1; i<=RecordArray.GetCount(); i++) {
    int top = (i-1)*22;
    CFieldRecord* pRecord = (CFieldRecord*)RecordArray.GetAt(i-1);
    CreateStatic(pRecord, top, 70+i); // 標籤
    
    // 外鍵或布林 -> ComboBox,其餘 -> Edit
    if (pRecord->IsBool || pRecord->pFK != NULL)
        CreateCombo(pRecord, top, 10+i);
    else
        CreateEdit(pRecord, top, 10+i);
}

ComboBox 會自動載入外鍵參考表的顯示欄位索引值。

5. 使用者輸入回寫與 SQL 生成

void CBMDialog::SendChangeMsg() {
    CString value, Msg;
    for (int i=1; i<=RecordArray.GetCount(); i++) {
        CWnd* pWnd = GetDlgItem(10+i);
        CFieldRecord* pRecord = (CFieldRecord*)RecordArray.GetAt(i-1);
        
        if (!pRecord->IsBool && pWnd->IsKindOf(RUNTIME_CLASS(CComboBox))) {
            CComboBox* pCombo = (CComboBox*)pWnd;
            CFldValue* p = (CFldValue*)pCombo->GetItemDataPtr(pCombo->GetCurSel());
            value = p->FieldValue; // 取得外鍵實際 ID
        } else {
            pWnd->GetWindowText(value);
        }
        if (value.Trim() == _T("")) { AfxMessageBox(pRecord->FieldName + _T("不能為空!")); return; }
        if (!(i==1 && !ISADD)) strcpy(pRecord->Value, value); // 修改模式下不動主鍵
    }
    CString cSend = ISADD ? GenerateInsertSql() : GenerateUpdateSql();
    ::SendMessage(m_hParent, WM_USER+51, 0, (LPARAM)&cSend);
}

6. 視圖類接收 SQL 並執行

LRESULT CStudentScoreView::OnExecSql(WPARAM, LPARAM lParam) {
    CString c = *((CString*)lParam);
    GetDocument()->m_BaseTblList.ExecSql(c);
    RefreshShow();
    return 0;
}

四、考試流程核心類實作

class CExam : public CObject {
public:
    CString m_No; COleDateTime m_Date; CString m_TermNo;
    CStringList& GetTerms(); CStringList* GetNos();
    void GetExamByNo(CString No);
    bool IsNoExisted(CString No);
    void AddExam(); void DeleteExamByNo(CString No);
protected:
    CADOStorage m_Storage; CString m_TblName;
private:
    CStringList m_TermList, m_NoList;
};

void CExam::AddExam() {
    CString FldList = _T("考試編號,考試日期,學期編號");
    CString ValueList = _T("'") + m_No + _T("','") + m_Date.Format() + _T("','") + m_TermNo + _T("'");
    m_Storage.ExecInsertSql(m_TblName, FldList, ValueList);
}

其他類 (CExamClass、CExamStudent、CExamSubject) 採相同模式,封裝各自資料表操作。

五、架構反思與改進方向

  • 優點:動態介面生成大幅減少重複程式碼,外鍵自動解析降低維護成本。
  • 缺點:每個業務類聚合獨立 CADOStorage,記憶體佔用較大;物件複製時連線狀態易發生衝突。
  • 建議:引入 Data Access Object (DAO) 模式或 Repository 模式,將資料存取邏輯集中於代理類,實體類保持純粹 (POCO),參考 Java Bean / Entity Framework 設計哲學。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved