数据库是指以一定的组织形式存放在计算机上的相互关联的数据的集合。一般一个库中有多个表组成,一张表中由多条记录组成,一条记录由若干字段组成。
例:
学生信息库——基本信息表、成绩表
基本信息表——每个学生的基本信息记录
基本信息记录——姓名、性别、年龄、专业等字段组成
DBMS数据库管理系统为用户提供对数据库操作的各种命令、工具及方法。
例:Access/Oracle/SQL Sever等等
专用于查询或修改关系型数据库的查询语言
句式 | 说明 |
---|---|
SELECT |
用于查询数据库,可以一次查询一个或多个表,选择需要的列,并设置查询条件,形成特定的查询结果集,这是最常用的SQL命令 |
CREATE |
主要用于在数据库中创建新表 |
DROP |
删除数据库中的表 |
INSERT |
向数据库中的表插入记录 |
DELETE |
删除数据库中表中的满足特定条件的记录 |
UPDATE |
修改数据库中表中的记录的值 |
从句 | 说明 |
---|---|
FROM 从句 |
用于指定要查询的表的名称,可以有多个表,各个表之间使用逗号(,)隔开 |
WHERE 从句 |
指定查询条件,只有那些满足条件的记录才包含在结果中 |
ORDER BY 从句 |
指定查询结果集按照指定的列的顺序排列,可以升序(ASC ),也可以降序(DESC ) |
运算符 | 说明 |
---|---|
AND |
逻辑“与”,即仅当两个条件都满足时才为真。 |
OR |
逻辑“或”,即仅当两个条件都不满足时才为假。 |
NOT |
逻辑“非”,即取反操作。 |
Visual C++为用户提供了ODBC、DAO及OLE DB三种数据库方式。这三种方式中最简单也最常用的是ODBC,因此本实验使用MFC的ODBC。
ODBC提供了应用程序接口API。使得任何一个数据库都可以通过ODBC驱动器与指定的DBMS相联。用户的程序可以通过调用ODBC驱动管理器中相应的驱动程序达到管理数据库的目的。访问数据库时,是由ODBC管理器将应用程序的数据库访问请示传递给相应的数据库驱动程序,驱动程序再用SQL语句完成DBMS的访问任务。
DAO使用Microsoft Jet提供的数据库访问对象集直接访问DBMS,速度比ODBC要快。
OLE DB是一种高性能的、基于COM(组件对象模型)的数据库技术。
开放数据库接口ODBC是一种标准的基于SQL的接口,提供了应用程序与数据库之间的接口,使得任何一个数据库都可以通过ODBC驱动器与指定的DBMS相联。使用ODBC能使用户编写数据库应用程序变得容易简单,避免了与数据库相连接的复杂性。ODBC的组成见下图:
ODBC管理器:位于控制面板
驱动程序管理器:ODBC32.dll
ODBC驱动程序:以DLL文件形式出现
MFC类库中提供了两个有关ODBC对数据库操作的类:CDatabase
和CRecordset
。
CDatabase
类:包含有数据库的连接信息,可以在整个应用程序中共享这些信息,一个Cdatabase
对象代表了一个与数据源的连接,通过它可以对数据源进行操作。
CRecordset
类:针对数据源中的记录集,负责对记录的操作。一个CRecordset
对象代表了一组从数据源查询出来的记录,称为记录集。记录集从数据源中查询出数据,然后再进行添加、排序等操作。
snapshot
):数据集的一个静态视图Dynaset
):能保持与其它用户所作的更改保持同步使用记录集对象,首先应建立与数据源的连接,这时应当创建并初始化CDatabase
对象,然后将创建的CDatabase
对象的指针传给CRecordset
对象的构造函数,这样记录对象创建成功,最后使用Open()
函数对数据源进行数据查询。
CRecordView
类:负责界面,以视图形式显示数据库记录。CRecordView
类是在控件中显示数据库记录的视图对象,是直接连接到CRecordView
对象上的表单视图。CDBException
:负责处理MFC ODBC在操作数据库时发生的异常。CRecordset
类CRecordset
对象代表从一个数据源选择的一组记录的集合,被称作“记录集”。CRecordset
对象可以以两种形式使用:动态集和快照。动态集是与其它用户的更新保持同步的动态数据集。快照是数据的静态视图。每一种形式都代表打开记录集时固定的一组记录。但是当滚动到动态集中的一个记录时,动态集将反映后来由其它用户或由应用程序中其它记录集对此记录所做的改变。
注意:
如果正在使用数据访问对象(DAO
)类,而不是打开数据库连接(ODBC
)类,请使用类CDaoRecordset
来代替。
要使用任何一种记录集,通常需要从CRecordset
派生一个应用程序指定的记录集类。记录集从一个数据源中选择记录,然后就可以:
打开一个数据库并构造一个记录集对象,给构造函数传递一个指向CDatabase
对象的指针。然后调用记录集的Open
成员函数,在此可以指定该对象是一个动态集还是一个快照。调用Open
来从数据源中选择数据。在记录集对象被打开之后,用它的成员函数和数据成员来滚动和操作记录。可用的操作根据对象是一个动态集还是一个快照(这依赖于打开数据库连接(ODBC)数据源的性能),是可更新的还是只读的,是否实现了成组行检取而不同。为了刷新从调用Open
以来可能被改变或添加的记录,可以调用对象的Requery
成员函数。当使用完对象之后,调用对象的Close
成员函数,并销毁此对象。
在一个派生的CRecordset
类中,使用记录字段交换(RFX)或成组记录字段交换(Bulk RFX)来支持读取和更新记录字段。
变量名 | 说明 |
---|---|
m_hstmt |
包含记录集的ODBC语句句柄。类型为HSTMT |
m_nFields |
包含记录集中的字段数据成员数目。类型为UINT |
m_nParams |
包含记录集中参数数据成员的数目。类型为UINT |
m_pDatabase |
包含一个指向CDatabase 对象的指针,提供该指针将记录集连接到一个数据源 |
m_strFilter |
包含一个CString ,此对象指定一条结构式查询语言(SQL)的WHERE子句。此成员可用作一个过滤器,只选择符合某一标准的那些记录 |
m_strSort |
包含一个CString ,此对象指定一条SQL ORDERBY 子句。此成员可用于控制记录的排序 |
函数 | 说明 |
---|---|
CRecordset |
构造一个CRecordset 对象。应用程序的派生类必须提供一个调用此函数的构造函数 |
Open |
通过检取记录集表示的表格或执行查询来打开记录集 |
Close |
关闭记录集和与此记录集相关联的ODBCHSTMT |
函数 | 说明 |
---|---|
CanAppend |
如果新记录可以通过 |
AddNew |
成员函数增加到记录集中,则该函数返回一个非零值 |
CanBookmark |
如果记录集支持书签则函数返回一个非零值 |
CanRestart |
如果可以调用Requery 来再次运行记录集的查询,则该函数返回一个非零值 |
CanScroll |
如果应用程序可以滚动记录,则该函数返回一个非零值 |
CanTransact |
如果数据源支持事务,则该函数返回一个非零值 |
CanUpdate |
如果记录集可修改(应用程序可以增加、修改或删除记录),则该函数返回一个非零值 |
GetODBCFieldCount |
返回记录集中的字段数目 |
GetRecordCount |
返回记录集中的记录数目 |
GetStatus |
获取记录集的状态:读取记录的索引,以及是否已获取到记录的最终计数 |
GetTableName |
获取此记录集基于的表的名字 |
GetSQL |
获取用于选择记录集的记录的SQL字符串 |
IsOpen |
如果前面已经调用了Open 函数,则此函数返回一个非零值 |
IsBOF |
如果记录集已经定位在第一个记录前,则此函数返回一个非零值 |
IsEOF |
如果记录集已经定位在最后一个记录后,则此函数返回一个非零值 |
IsDeleted |
如果记录集定位在一个已删除的记录上,则该函数返回一个非零值 |
函数 | 说明 |
---|---|
AddNew |
为增加新记录作准备。调用Update 来完成增加 |
CancelUpdate |
取消任何用AddNew 或Edit 操作指定的未决定的更新 |
Delete |
从记录集中删除当前记录。删除之后,应用程序必须显式地滚动到另一个记录 |
Edit |
为改变当前记录作准备。调用Update 来完成编辑 |
Update |
通过将新数据或所编辑的数据保存到数据源上,来完成一次AddNew 或Edit 操作 |
函数 | 说明 |
---|---|
GetBookMark |
将一个记录的标签值分配给该参数对象 |
Move |
将记录集双向定位到距离当前记录指定数目的记录的位置 |
MoveFirst |
定位当前记录为记录集中的第一个记录。该函数首先测试IsBOF |
MoveLast |
定位当前记录为记录集中的最后一个记录。该函数首先测试IsEOF |
MoveNext |
定位当前记录为记录集中的下一个记录。该函数首先测试IsEOF |
MovePrev |
定位当前记录为记录集中的第一个记录。该函数首先测试IsBOF |
SetAbsolutePosition |
将记录集定位到与指定的记录数相对应的位置 |
SetBookmark |
定位记录集到书签指定的位置 |
函数 | 说明 |
---|---|
Cancel |
取消一次异步操作或一次来自第二线程的处理 |
FlushResultSet |
当使用一个预定义的查询时,如果有另外一个结果被获取,返回非零值 |
GetFieldValue |
返回记录集中的一个字段的值 |
GetODBCFieldInfo |
返回记录集中各字段的指定类别的信息 |
GetRowsetSize |
返回在一次单个获取中你要获取的记录数目 |
GetRowsFetched |
返回在一次获取中实际获取的行数 |
GetRowStatus |
返回在一次获取中行的状态 |
IsFieldDirty |
如果在当前记录中的指定字段被改变,则返回一个非零值 |
IsFieldNull |
如果当前记录中的指定字段是Null (没有值),则返回非零值 |
IsFieldNullable |
如果当前记录中的指定字段可被设置为Null (没有值),则返回非零值 |
RefreshRowset |
刷新指定行的数据和状态 |
Requery |
再次运行记录集的查询来刷新所选择的记录 |
SetFieldDirty |
标记当前记录中的指定字段是被改变的 |
SetFieldNull |
设置当前记录中的指定字段的值为Null (没有值) |
SetLockingMode |
将加锁模式设置为“乐观”加锁(缺省值)或“悲观”加锁。确定任何更新加锁记录 |
SetParamNull |
将指定的参数设置为Null (没有值) |
SetRowsetCursorPosition |
将游标定位在记录集中的指定行上 |
函数 | 说明 |
---|---|
Check |
用来检查从一个ODBC API函数返回的代码 |
CheckRowSetError |
用来处理在获取记录期间产生的错误 |
DoBulkFieldExchange |
用来将一组数据行从数据源中交换到记录集中。实现成组记录交换(BulkRFX ) |
DoFieldExchange |
用来在此记录集的字段数据成员和数据源上对应的记录之间交换数据(双向)。双向记录字段交换(RFX ) |
GetDefaultConnect |
用来获取缺省的字符串 |
GetDefaultSQL |
用来获取要执行的缺省的SQL字符串 |
OnSetOptions |
用来为指定的ODBC语句设置选项 |
SetRowsetSize |
指定在一次获取中你希望获取的记录数目 |
CDatabase
类CDatabase
对象表示到数据源的连接,通过它可以操作数据源。数据源是位于一些数据库管理系统(DBMS)的数据的指定实例,包括MicrosoftSQL Server,Microsoft Access,Borland dBASE和xBASE。在应用中可以同时使一个或多个CDatabase
对象活动。
注意:如果在处理数据访问对象(DAO)类而不是开放数据库连接(ODBC)类,可使用类CDaoDatabase
。为使用CDatabase
,构造一个CDatabase
对象并调用它的OpenEx
成员函数。这打开了一个连接。在接着构造CRecordset
对象以操纵连接的数据源时,向CDatabase
对象传递记录集构造程序指针。完成使用连接时调用Close
成员函数并销毁CDatabase
对象。Close
关闭以前没有关闭的任何记录集。
函数 | 说明 |
---|---|
CDatabase |
构造一个CDatabase 对象。必须通过调用OpenEx 或Open 初始化这个对象 |
Open |
建立到数据源的一个连接(通过ODBC驱动程序) |
OpenEx |
建立到数据源的一个连接(通过ODBC驱动程序) |
Close |
关闭数据源连接 |
函数 | 说明 |
---|---|
GetConnect |
返回用于连接CDatabase 对象和数据源的ODBC连接字符串 |
IsOpen |
如果CDatabase 对象当前与数据源连接,则返回非零 |
GetDatabaseName |
返回当前使用的数据库名字 |
CanUpdate |
如果CDatabase 可更新(不是只读的),则返回非零 |
CanTransact |
如果数据源支持事务,则返回非零 |
SetLoginTimeout |
设置数据源连接试图超时的秒数 |
SetQueryTimeout |
设置数据库查询操作超时的秒数。影响以后的所有记录集调用:Open ,AddNew ,Edit 和Delete |
GetBookmarkPersistence |
标识记录集对象上书签持久化操作 |
GetCursorCommitBehavior |
标识在打开的记录集对象上提交事务的效果 |
GetCursorRollbackBehavior |
标识在打开的记录集对象上回滚事务的效果 |
函数 | 说明 |
---|---|
BeginTrans |
在连接的数据源上开始“事务”──类CRecordset 的一系列可回滚的AddNew ,Edit ,Delete 和Update 成员函数调用。数据源必须支持事务才能使BeginTrans 有效 |
BindParameters |
允许在调用CDatabase::ExecuteSQL 前绑定参数 |
CommitTrans |
完成由从BeginTrans 开始的事务。执行这个事务中改变数据源的命令 |
Rollback |
回滚当前事务期间所做变化,数据源返回到BeginTrans 调用时定义的未改变的以前状况 |
Cancel |
取消第二个线程的异步操作或处理 |
ExecuteSQL |
执行一条SQL语句。不返回数据记录 |
ADO(ActiveX Data Object)是Microsoft的数据库应用程序开发的新接口,是建立在OLE DB之上的高级数据库访问技术。
ADO技术基于COM(Component Object Model),具有COM组件的诸多优点,可以用来构造可复用应用框架,被多种语言支持,能够访问关系数据库、非关系数据库以及所有的文件系统。
另外,ADO还支持各种客户/服务器模式与基于Web的数据操作,具有远程数据服务RDS(Remote Data Service)的特性,是远程数据存取的发展方向。
ADO对象模型提供了7种对象、4种集合。分别为:
Connection
Command
Parameter
Recordset
Field
Property
Errors
集合Parameters
集合Fields
集合Properties
集合ADO模型中常用的对象:Connection对象、Command对象、Recordset对象,在使用这3个对象的时候,需要定义与之对应的3个智能指针,分别为
_ConnectionPtr
_CommandPtr
_RecordsetPtr
掌握MFC的ODBC和ADO链接后台数据库的编程方法和技巧。
创建一个支持ODBC或者ADO的数据库的SDI应用程序:
打开MFC应用程序向导,选择单文档、MFC标准后进入数据库支持页面,设置如下:
然后点击“数据源”后选择机器数据源,然后选择刚刚在控制面板添加的数据源即可。
输入电脑的登录用户与密码后即可创建项目。
首先创建5个文本编辑框用于显示数据(因为本实验中只有5个字段),并依次添加按钮用于功能实现:
根据数据库源文件中相应的成员
void CEx7Set::DoFieldExchange(CFieldExchange* pFX)
{
pFX->SetFieldType(CFieldExchange::outputColumn);
// RFX_Text() 和 RFX_Int() 这类宏依赖的是
// 成员变量的类型,而不是数据库字段的类型。
// ODBC 尝试自动将列值转换为所请求的类型
RFX_Long(pFX, _T("[编号]"), column1);
RFX_Text(pFX, _T("[studentnum]"), m_studentnum);
RFX_Text(pFX, _T("[studentname]"), m_studentname);
RFX_Text(pFX, _T("[subject]"), m_subject);
RFX_Long(pFX, _T("[mark]"), m_mark);
}
在视图类里添加相应绑定函数即可:
void CEx7View::DoDataExchange(CDataExchange* pDX)
{
CRecordView::DoDataExchange(pDX);
// 可以在此处插入 DDX_Field* 函数以将控件“连接”到数据库字段,例如
// DDX_FieldText(pDX, IDC_MYEDITBOX, m_pSet->m_szColumn1, m_pSet);
// DDX_FieldCheck(pDX, IDC_MYCHECKBOX, m_pSet->m_bColumn2, m_pSet);
// 有关详细信息,请参阅 MSDN 和 ODBC 示例
DDX_FieldText(pDX, IDC_EDIT1, m_pSet->column1, m_pSet);
DDX_FieldText(pDX, IDC_EDIT2, m_pSet->m_studentnum, m_pSet);
DDX_FieldText(pDX, IDC_EDIT3, m_pSet->m_studentname, m_pSet);
DDX_FieldText(pDX, IDC_EDIT4, m_pSet->m_subject, m_pSet);
DDX_FieldText(pDX, IDC_EDIT5, m_pSet->m_mark, m_pSet);
DDX_Text(pDX, IDC_EDIT6, m_strQuery);
}
// 上一条
void CEx7View::OnBnClickedButton1()
{
if (m_pSet->IsOpen())
{
m_pSet->MovePrev();
if (m_pSet->IsBOF())
MessageBox(TEXT("当前已经为第1条记录!"));
else
UpdateData(FALSE);
}
}
// 下一条
void CEx7View::OnBnClickedButton2()
{
if (m_pSet->IsOpen())
{
m_pSet->MoveNext();
if (m_pSet->IsEOF())
MessageBox(TEXT("当前已经为最后一条记录!"));
else
UpdateData(FALSE);
}
}
// 最后一条
void CEx7View::OnBnClickedButton3()
{
if (m_pSet->IsOpen())
{
m_pSet->MoveLast();
UpdateData(FALSE);
}
}
// 最初一条
void CEx7View::OnBnClickedButton4()
{
if (m_pSet->IsOpen())
{
m_pSet->MoveFirst();
UpdateData(FALSE);
}
}
首先定义一个设定各属性值为空的宏:
#define SET_FIELD_NULL \
m_pSet->SetFieldNull(&(m_pSet->column1), FALSE); \
m_pSet->SetFieldNull(&(m_pSet->m_studentnum), FALSE); \
m_pSet->SetFieldNull(&(m_pSet->m_studentname), FALSE); \
m_pSet->SetFieldNull(&(m_pSet->m_subject), FALSE); \
m_pSet->SetFieldNull(&(m_pSet->m_mark), FALSE);
接着实现数据的添加:
// 添加
void CEx7View::OnBnClickedButton5()
{
m_pSet->AddNew();
SET_FIELD_NULL
UpdateData(TRUE);
m_pSet->Update();
m_pSet->Requery();
MessageBox(TEXT("添加成功!"));
}
// 删除
void CEx7View::OnBnClickedButton7()
{
if (MessageBox(TEXT("真的要删除该记录吗?"), TEXT("删除记录"), MB_YESNO | MB_ICONQUESTION) == IDYES)
{
m_pSet->Delete();
m_pSet->MoveNext();
if (m_pSet->IsEOF())
m_pSet->MoveLast();
if (m_pSet->IsBOF())
m_pSet->SetFieldNull(NULL);
UpdateData(FALSE);
MessageBox(TEXT("删除成功!"));
}
}
// 修改
void CEx7View::OnBnClickedButton6()
{
m_pSet->Edit();
UpdateData(TRUE);
m_pSet->Update();
m_pSet->Requery();
MessageBox(TEXT("修改成功!"));
}
// 查询
void CEx7View::OnBnClickedButton8()
{
UpdateData(TRUE);
m_pSet->m_strFilter = TEXT("[studentname]='") + m_strQuery + TEXT("'");
m_pSet->Requery();
UpdateData(FALSE);
}
// 按成绩排序
void CEx7View::OnBnClickedButton9()
{
m_pSet->m_strSort = "mark";
m_pSet->Requery();
UpdateData(FALSE);
}
我使用的是visual studio 2013,可能由于版本不兼容的原因,如果使用64位“ODBC数据源管理器”,在创建项目的时候会导致数据源链接失败,使用32位的可以解决问题。
在创建项目的时候需要选择类型为“动态集”,如果选择“快照”会导致只读异常。
在完成这个MFC的ODBC和ADO数据库连接的实验过程中,我学到了许多关于MFC框架和数据库编程的知识和技巧。
在实验开始阶段,需要注册数据库以及在应用程序中建立与数据库的连接。这一步是整个数据库编程的基础,需要确保正确配置ODBC数据源,并在应用程序中正确设置连接信息。通过MFC的数据视图类和数据交换机制,将数据库中的字段与界面上的控件关联起来。这样,可以通过控件直接显示和修改数据库中的数据,使用户界面与数据库之间实现了双向的数据交互。编写代码实现对数据库中数据的显示和操作是关键的一步。例如,通过MoveNext
、MovePrev
等函数实现记录的导航,通过AddNew
、Edit
、Update
等函数实现对记录的添加、修改和删除。实现了按条件查询和按特定字段排序的功能,这是实际应用中常见的需求。在这个实验中,通过设置过滤条件和排序方式,可以在界面上方便地对数据进行筛选和排序。在操作数据库的过程中,需要考虑到可能出现的异常情况,例如记录已经是第一条或最后一条时的处理。通过合理的异常处理,可以提高程序的稳定性和用户体验。在实验中,使用了宏来简化一些重复的操作,例如设置字段为空。这种技巧有助于提高代码的可维护性和可读性。实验中综合运用了多种数据库操作,包括导航、修改、添加、删除、查询和排序等。这样的实践有助于深入理解数据库编程的全过程。
总的来说,通过这个实验,我对MFC框架下使用ODBC和ADO连接数据库的方法有了更深入的理解,并学到了在实际项目中如何设计和实现与数据库的交互功能。这些知识和经验对于日后从事Windows平台下的应用程序开发将会有很大的帮助。
代码地址:https://github.com/zsc118/MFC-exercises