NoteBook的重点也是难点的地方在数据库的使用。当我需要把编辑好的内容存储到数据库,当打开软件,读出数据的时候也需要访问数据库。所以和数据库的交互是本程序的精髓,然而在设计数据库的时候却遇到了麻烦。
当需要浏览或者创建新的记录的时候就需要通过树型控件来进行选择,该树可以这样生成
|--根类别
| |
| |----子类别
| | |
| | |----子记录
| |
| |----子记录
|
|--根记录
也就是说我们的数据库存储要根据树型的构造来存储数据,想了很久也得不到答案。后来,今天问了一个搞了10多年数据库的同事,终于得到了一种比较满意的答案。建立一张表,然后建一个字段“父ID”,每个子类别或者子记录对应一个ID号(这个ID号是自动的),根类别或者根记录的“父ID”就为0,也即空。这样就可以把整棵数的内容存储到数据库了,当然,还需要其他的一些字段如:标题、内容、日期,等等和记录相关的字段。
好了,有了数据库,就可以封装一个类来进行数据库的添加、删除、修改操作了。利用ADO的连接方式,下面给出了这个类的具体代码:
- #pragma once
- #include <vector>
- #include "StdAfx.h"
- using std::vector;
- // 数据成员
- class Item
- {
- public:
- int m_nID; // ID
- CString m_strTitle; // 标题
- CString m_strTime; // 时间
- CString m_strKey; // 关键字
- CString m_strContext; // 内容
- CString m_strMark; // 备注
- int m_nParentID; // 父亲ID
- };
- class CDataBase
- {
- public:
- CDataBase(CString strName);
- virtual ~CDataBase(void);
- public:
- void Init(); // 初始化数据库
- void Destroy(); // 销毁数据库连接
- private:
- _ConnectionPtr m_pConnection; // 连接数据库指针
- _CommandPtr m_pCommand; // 命令集指针
- _RecordsetPtr m_pRecordset; // 记录集指针
- CString m_strDatabaseName;// 数据库名称
- 保存所有的数据
- BOOL Load(); // 加载所
- public:
- BOOL Add(Item &item); // 添加一条记录
- BOOL Delete(int nID); // 删除一条记录
- //BOOL Modify()
- };
- vector<Item> m_vecItem; // 数据容器
- // 接口
- public:
- BOOL Save(); // 有的数据
-
- #include "StdAfx.h"
- #include "DataBase.h"
- CDataBase::CDataBase(CString strName)
- : m_strDatabaseName(strName)
- , m_pConnection(NULL)
- , m_pCommand(NULL)
- , m_pRecordset(NULL)
- {
- Init();
- }
- CDataBase::~CDataBase(void)
- {
- Destroy();
- }
- // 初始化数据库
- void CDataBase::Init()
- {
- m_pConnection.CreateInstance(__uuidof(Connection));
- try
- {
- // 打开本地Access库NoteBook.mdb
- CString strSQL=_T("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=")+m_strDatabaseName+_T(".mdb");
- m_pConnection->Open(_bstr_t(strSQL),_T(""),_T(""),adModeUnknown);
- m_pCommand.CreateInstance(__uuidof(Recordset));
- m_pRecordset.CreateInstance(__uuidof(Recordset));
- try
- {
- // 获取所有字段数据
- m_pRecordset->Open(_T("SELECT * FROM NoteBook"),m_pConnection.GetInterfacePtr(),
- adOpenDynamic,adLockOptimistic,adCmdText);
- }
- catch(_com_error &e)
- {
- AfxMessageBox(e.ErrorMessage());
- return;
- }
- }
- catch(_com_error &e)
- {
- AfxMessageBox(e.ErrorMessage());
- return;
- }
- }
- // 销毁释放资源
- void CDataBase::Destroy()
- {
- if( m_pRecordset!=NULL )
- {
- m_pRecordset->Close();
- m_pRecordset.Release();
- m_pRecordset=NULL;
- }
- if( m_pCommand!=NULL )
- {
- m_pCommand.Release();
- m_pCommand=NULL;
- }
- if( m_pConnection->State )
- {
- m_pConnection->Close();
- m_pConnection.Release();
- m_pConnection= NULL;
- }
- }
- // 加载数据库数据
- BOOL CDataBase::Load()
- {
- if( m_pRecordset==NULL )
- return FALSE;
- _variant_t varID,varTitle,varTime,varKey,varContext,varMark,varParentID;
- Item item;
- try
- {
- if( !m_pRecordset->BOF )
- m_pRecordset->MoveFirst();
- else
- {
- AfxMessageBox(_T("表内数据为空"));
- return FALSE;
- }
- // 读出库中各字段数据
- while( !m_pRecordset->adoEOF )
- {
- varID = m_pRecordset->GetCollect(_T("ID"));
- if(varID.vt != VT_NULL)
- item.m_nID = (long)(varID);
- varTitle = m_pRecordset->GetCollect(_T("标题"));
- if(varTitle.vt != VT_NULL)
- item.m_strTitle = (LPCSTR)_bstr_t(varTitle);
- varTime = m_pRecordset->GetCollect(_T("日期"));
- if(varTime.vt != VT_NULL)
- item.m_strTime = (LPCSTR)_bstr_t(varTime);
- varKey = m_pRecordset->GetCollect(_T("关键字"));
- if(varKey.vt != VT_NULL)
- item.m_strKey = (LPCSTR)_bstr_t(varKey);
- varContext = m_pRecordset->GetCollect(_T("内容"));
- if(varContext.vt != VT_NULL)
- item.m_strKey = (LPCSTR)_bstr_t(varContext);
- varMark = m_pRecordset->GetCollect(_T("备注"));
- if(varMark.vt != VT_NULL)
- item.m_strMark = (LPCSTR)_bstr_t(varMark);
- varParentID = m_pRecordset->GetCollect(_T("父亲"));
- if(varParentID.vt != VT_NULL)
- item.m_nParentID = (long)(varParentID);
- // 送到容器
- m_vecItem.push_back(item);
- // 移动到下一条记录
- m_pRecordset->MoveNext();
- }
- }
- catch(_com_error &e)
- {
- AfxMessageBox(e.ErrorMessage());
- return FALSE;
- }
- return TRUE;
- }
- // 存储到数据库
- BOOL CDataBase::Save()
- {
- if( m_pRecordset==NULL )
- return FALSE;
- try
- {
- m_pCommand->ActiveConnection = m_pConnection;
- m_pCommand->CommandText=_T("TRUNCATE TABLE NoteBook");
- m_pCommand->Execute(NULL,NULL,adCmdText); // 执行SQL语句
- // 写入各字段值
- m_pRecordset->AddNew();
- for(vector<Item>::iterator iter=m_vecItem.begin(); iter!=m_vecItem.end(); ++iter)
- {
- m_pRecordset->PutCollect(_T("标题"), _variant_t(iter->m_strTitle));
- m_pRecordset->PutCollect(_T("日期"), _variant_t(iter->m_strTime));
- m_pRecordset->PutCollect(_T("关键字"), _variant_t(iter->m_strKey));
- m_pRecordset->PutCollect(_T("内容"), _variant_t(iter->m_strContext));
- m_pRecordset->PutCollect(_T("备注"), _variant_t(iter->m_strMark));
- m_pRecordset->PutCollect(_T("父亲"), _variant_t(iter->m_nParentID));
- }
- m_pRecordset->Update();
- }
- catch(_com_error *e)
- {
- AfxMessageBox(e->ErrorMessage());
- return FALSE;
- }
- return TRUE;
- }
- // 增加记录
- BOOL CDataBase::Add(Item &item)
- {
- if( m_pRecordset==NULL )
- return FALSE;
- // 向容器添加记录
- m_vecItem.push_back(item);
- try
- {
- // 向数据库增加记录
- m_pRecordset->AddNew();
- m_pRecordset->PutCollect(_T("标题"), _variant_t(item.m_strTitle));
- m_pRecordset->PutCollect(_T("日期"), _variant_t(item.m_strTime));
- m_pRecordset->PutCollect(_T("关键字"), _variant_t(item.m_strKey));
- m_pRecordset->PutCollect(_T("内容"), _variant_t(item.m_strContext));
- m_pRecordset->PutCollect(_T("备注"), _variant_t(item.m_strMark));
- m_pRecordset->PutCollect(_T("父亲"), _variant_t(item.m_nParentID));
- m_pRecordset->Update();
- }
- catch(_com_error *e)
- {
- AfxMessageBox(e->ErrorMessage());
- return FALSE;
- }
- return TRUE;
- }
- // 删除指定记录
- BOOL CDataBase::Delete(int nID)
- {
- try
- {
- CString strSQL;
- strSQL.Format(_T("DELETE FROM NoteBook WHERE ID=%d"),nID);
- m_pCommand->ActiveConnection = m_pConnection;
- m_pCommand->CommandText=(_bstr_t)strSQL;
- m_pCommand->Execute(NULL,NULL,adCmdText); // 执行SQL语句
- }
- catch(_com_error *e)
- {
- AfxMessageBox(e->ErrorMessage());
- return FALSE;
- }
- return TRUE;
- }
这样,我们的操作数据库就简单了。就可以通过树型控件来进行数据库的访问操作了!
后记:在测试这个类的时候,发现退出程序会出现recordset异常,察看了调用堆栈,居然说recordset的Close方法错误?
My GOD! 怎么可能呢?recordset智能指针时需要先Close再Release的嘛!,后来检查Destroy函数,才发现原来是connection指针已经释放了,导致recordset指针出错!把释放顺序改成先recordset再connection就没问题了~具体什么原因,也不想追踪了,但是一定记得--释放recordset指针的时候一定要赶在connection指针释放前!!