数据库开发
数据库(Database)是按照数据结构来组织、存储和管理数据的仓库,它产生于距今五十年前,随着信息技术和市场的发展,特别是二十世纪九十年代以后,数据管理不再仅仅是存储和管理数据,而转变成用户所需要的各种数据管理的方式。数据库有很多种类型,从最简单的存储有各种数据的表格到能够进行海量数据存储的大型数据库系统都在各个方面得到了广泛的应用。本章主要讲解基于Windows Embedded Compact 7的数据库开发。
8.1 数据库的基本介绍
数据库是以一定组织方式储存在一起的,能为多个用户共享的,具有尽可能小的冗余度的、与应用彼此独立的相互关联的数据集合,数据库是存储数据的重要方式,已经应用到多种不同的场合。Windows CE系统支持两种类型的数据库:内嵌的CE数据库(CEDB)和嵌入式数据库EDB。其中CEDB是较老的数据库引擎,已无法满足现在如此大量和复杂要求的数据处理,不过为了向下兼容,还是保留在Windows CE系统中。CEDB是从Windows CE5.0开始引入的一个新的高效的数据库引擎,能够支持事务以及更大数据量的流式访问。
在Windows CE中,数据库被组织在数据库卷中,首先需要将数据库挂载到系统的某个目录下,才能访问。一个数据库卷可以包含多个不同类型的数据库,每个数据库包含多条记录,每个记录可以包含多个属性,实际上可以看作一张表,每行代表一个记录,每列代表记录内的一个属性。每个属性可以指定为不同的数据类型,在EDB中可以支持13种数据类型,其中包含9个CEDB也支持以及4个只有EDB才能支持的类型,表8-1列出了EDB中支持的数据类型。
表8-1 EDB支持的数据类型
数据类型 | 说明 |
CEVT_BLOB | CEBLOB结构,代表字节集合 |
CEVT_BOOL | 布尔类型 |
CEVT_FILETIME | 日期时间结构 |
CEVT_I2 | 16位有符号整数 |
CEVT_I4 | 32位有符号整数 |
CEVT_LPWSTR | 以null结尾的Unicode字符串 |
CEVT_R8 | 64位的浮点实数 |
CEVT_UI2 | 16位无符号整数 |
CEVT_UI4 | 32位无符号整数 |
CEVT_STREAM | 只有EDB支持,一个大的二进制数据流 |
CEVT_RECID | 只有EDB支持,一个128位的代表记录标识的GUID |
CEVT_AUTO_I4 | 只有EDB支持,自动生成的32位有符号整数 |
CEVT_AUTO_I8 | 只有EDB支持,自动生成的64位有符号整数 |
其中CEVT_STREAM数据类型对于访问大的数据对象十分有用,比如图片,e-mail中的附件,由于流式访问的数据是单独存储在数据库中的数据页面上,而不是放在数据库的某一行中,流式访问比较高效。EDB中限制每个记录最大为8KB,而流对象不受单个记录大小的限制,流对象最大可以为32MB,后面将介绍如何使用流对象来读写记录的数据。
下面我们重点介绍EDB数据库开发的API。
8.2 EDB数据库开发API
8.2.1 挂载及卸载数据库卷
如果数据库卷以文件的形式存放在某个外部存储设备上,那么在打开数据库之前需要先挂载数据库卷,在关闭数据库后,需要卸载数据库卷。Win 32 API提供的CeMountDBVolEx()函数用于挂载数据库卷,它既可创建一个新的卷,也可以打开一个已经存在的卷:
BOOL CeMountDBVolEx(
PCEGUID pGuid,
LPWSTR lpwszDBVol,
CEVOLUMEOPTIONS* pOptions,
DWORD dwFlags
);
l 参数pGuid为一个输出参数,指向一个CEGUID数据类型的指针,用于返回挂载数据库卷的全局标识符,CEGUID用于追踪已打开数据库卷的句柄。、
l 参数lpszDBVol指定将被挂载的数据库卷所在的文件名,一个数据库卷可以包含多个数据库。
l 参数pOptions为一个指向结构体CEVOLUMEOPTIONS的指针,用于在数据库卷被第一次挂载时设置数据库卷的管理方式。参数pOptions可以设置为NULL,将使用系统的默认管理方式。结构体CEVOLUMEOPTIONS的定义如下:
typedef struct CEVOLUMEOPTIONS {
WORD wVersion;
DWORD cbBufferPool;
DWORD dwAutoShrinkPercent;
DWORD dwFlushInterval;
DWORD cMaxNotifyChanges;
DWORD dwDefaultTimeout;
WCHAR wszPassword[CCH_MAX_PASSWORD + 1];
DWORD dwFlags;
} CEVOLUMEOPTIONS;
n 成员wVersion代表结构体的版本,必须设置为1。
n 成员cbBufferPool代表所用缓冲池的字节数。
n 成员dwAutoShrinkPercent代表自动收缩百分比,当数据库卷内的可用空间低于这个值时,操作系统会触发自动收缩线程,回收可用空间。
n 成员dwFlushInterval代表数据库引擎的自动回写时间间隔,单位为秒。
n 成员cMaxNotifyChanges代表对于一个事务可以缓存在队列中的最大更改数量。
n 成员dwDefaultTimeout代表超时时间,单位为毫秒。
n 成员wszPassword代表数据库卷的密码。
n 成员dwFlags代表结构体中包含有效值的成员,其它的成员将使用系统默认值。
l 参数dwFlags指定挂载数据库卷的方式,可取值如表8-1所列:
表8-1 挂载数据库卷的可取方式
可取值 | 说明 |
CREATE_ALWAYS | 总是创建新的数据库卷,如果指定名字的数据库卷已经存在,则将其覆盖 |
CREATE_NEW | 新建一个数据库卷,如果指定名字的数据库卷已经存在,则函数调用将失败 |
OPEN_ALWAYS | 总是打开数据库卷,如果指定名字的数据库卷不存在,就先新创建一个 |
OPEN_EXISTING | 打开数据库卷,如果不存在,则函数调用将失败 |
TRUNCATE_EXISTING | 打开数据库卷,并删除数据库卷上的全部数据,包括所有数据库和记录。如果指定名字的数据库卷不存在,函数调用将失败 |
如果挂载数据库卷成功,函数将返回TRUE,参数pceguid将返回挂载数据库卷的CEGUID,这个将被用于多个数据库操作的函数中;返回FALSE表示挂载失败,调用GetLastError()函数进一步获取错误信息,可取的错误信息如下:
l ERROR_ALREADY_EXISTS: 函数调用成功,而且数据库在函数调用之前已经存在,此时参数dwFlags被设置为OPEN_ALWAYS或CREATE_ALWAYS。
l ERROR_ACCESS_DENIED: 代表如下错误,参数dwFlags被设置为TRUNCATE_EXISTING而且数据库卷已经被挂载或文件不允许被截短,或者数据库卷为只读。
l ERROR_BAD_FORMAT: 数据库卷被指定为其它版本的EDB。
l ERROR_FILE_NOT_FOUND: 指定为TRUNCATE_EXISTING但是卷不存在。
l ERROR_INVALID_PARAMETER: 指定了错误的参数,如将参数pGuid设置为NULL。
l ERROR_WRONG_PASSWPRD: 错误的数据库卷密码。
注意,在Windows CE中,允许一个数据库卷被多次挂载,每被挂载一次,数据库卷的引用计数就会自动加1,而调用CeUnmountDBVol()函数实际上将引用计数减1,直到应用计数减为0,数据库卷才被从系统中卸载。如果一个数据库卷已经被挂载,则不允许新创建该名字的数据库卷或将数据库卷清空,将返回ERROR_ACCESS_DENIED错误。
使用完数据库卷之后,一定要记得将其卸载,这样才能释放资源,卸载数据库卷调用函数CeUnmountDBVol()实现:
BOOL CeUnmountDBVol(
PCEGUID pGuid
);
l 参数pGuid指定需要卸载的数据库卷的CEGUID,就是我们前面挂载数据库卷返回的全局标识。
如果卸载数据库卷成功,函数返回TRUE,而且所有缓存的对数据库卷的更新将永久写回到存储设备中。挂载失败将返回FALSE。一旦将数据库卷挂载,标识CEGUID将无效,不能再使用。
8.2.2 枚举数据库卷
应用程序可以枚举系统中当前正被挂载的所有数据库卷的CEGUID和名字,函数CeEnumDBVolumes()实现枚举功能:
BOOL CeEnumDBVolumes(
PCEGUID pGuid,
LPWSTR pwszName,
DWORD cchMaxName
);
l 参数pGuid为输入输出参数,返回下一个数据库卷的CEGUID。
l 参数pwszName为输出参数,用于返回下一个数据库卷的名字,为一个指向内存缓冲区的指针。
l 参数cchMaxName指定上面缓冲区的大小,单位为字节,至少应该设置为CEDB_MAXDBASENAMELEN,
函数调用成功将返回TRUE,失败将返回FALSE。如果需要查询具体的错误信息,可进一步调用GetLastError()函数,可能的值有:
l ERROR_INSUFFICIENT_BUFFER: 指定的缓冲区空间不够。
l ERROR_INVALID_PARAMETER: 其中的某个参数非法,参数pGuid或pwszName被设置为NULL。
l ERROR_NO_MORE_ITEMS: 没有新的挂载数据库卷,这个将被用于判断枚举是否结束。
l ERROR_KEY_DELETED: 枚举挂载数据库卷只能从一个非法的GUID或一个已经挂载的卷开始。其它的GUID都将失败,比如使用一个已经被删除的卷的GUID。
这个函数的使用方式比较特殊,为了得到系统中第一个已挂载的数据库卷信息,第一次调用该函数需要通过CREATE_INVALIDGUID宏将参数pceguid设定为无效值。函数调用成功后,pceguid将返回数据库卷对应的CEGUID。为了枚举下一个数据库卷的信息,需要将上次调用返回的pceguid参数传入,再次调用该函数即可。这样循环调用直到返回FALSE为止,就能枚举挂载的所有数据库卷。下面的代码演示枚举过程:
CEGUID ceguid;
TCHAR szVolume[CEDB_MAXDBASENAMELEN];
CREATE_INVALIDGUID(&ceguid);
While(CeEnumDBVolumes(&ceguid, szVolume, sizeof(szVolume)))
{
//输出挂载的数据库卷信息
}
8.2.3 枚举数据库
如果希望进一步查看每个挂载的数据库卷内包含的数据库信息,可以通过EDB提供另外两个的API函数实现:CeFindFirstDatabaseEx()函数返回数据库卷内的第一个数据库的查找句柄,然后使用这个查找句柄调用CeFindNextDatabaseEx()继续查找数据库。
CeFindFirstDatabaseEx()函数的原型如下:
HANDLE CeFindFirstDatabaseEx(
PCEGUID pGuid,
DWORD dwDbaseType
);
l 参数pGuid指定数据库卷的CEGUID。这个参数可以设置为NULL或由CREATE_INVALIDEDBGUID宏产生,就表示将对所有的挂载数据库卷进行枚举;否则只枚举指定的数据库卷。
l 参数dwDbaseType指定枚举数据库的类型。如果这个值设置为0,将枚举数据库卷内所有类型的数据库,否则只枚举指定类型的数据库。
函数调用成功,将返回一个用于枚举数据库上下文的查找句柄;失败将返回INVALID_HANDLE_VALUE。
CeFindNextDatabaseEx()函数返回满足枚举类型的数据库的对象ID,函数原型为:
CEOID CeFindNextDatabaseEx(
HANDLE hEnum,
PCEGUID pGuid
);
l 参数hEnum为数据库的查找句柄。
l 参数pGuid为输出参数,如果枚举数据库成功,返回下一个数据库的GUID。如果不需要,可以将这个参数设置为NULL。
函数调用成功,返回下一个数据库的对象ID;失败则返回0。如果没有其它的数据库可以枚举,函数将返回0。枚举数据库的方法为,不断调用CeFindNextDatabaseEx()函数,直到函数返回0为止,下面的代码演示如何枚举一个数据库卷内所有数据库:
HANDLE hDBList;
CEOID oidDB;
hDBList = CeFindFirstDatabaseEx (&ceguid,0);
if (hDBList != INVALID_HANDLE_VALUE)
{
oidDB = CeFindNextDatabaseEx (hDBList,&ceguid);
//判断是否达到枚举最后
while (oidDB)
{
//对数据库进行操作
//继续往下枚举数据库
oidDB = CeFindNextDatabaseEx (hDBList,&ceguid);
}
//枚举数据库完毕,关闭数据库查找句柄
CloseHandle (hDBList);
8.2.4 查询对象信息
在Windows CE中,数据库和记录都被看作是一个对象,数据库的信息通过查询对象信息的函数CeOidGetInfoEx2()获取:
BOOL CeOidGetInfoEx2(
PCEGUID pGuid,
CEOID oid,
CEOIDINFO* poidInfo
);
l 参数pGuid指定包含数据库对象的挂载数据库卷的CEGUID。
l 参数oid指定查询的数据库对象ID。
l 参数poidInfo为输入输出参数,这是一个指向结构体CEOIDINFOEX的指针,用于接收返回的数据库对象信息。结构体CEOIDINFOEX的定义如下:
typedef struct CEOIDINFOEX {
WORD wVersion;
WORD wObjType;
union {
CEFILEINFO infFile;
CEDIRINFO infDirectory;
CEDBASEINFO infDatabase;
CERECORDINFO infRecord;
};
} CEOIDINFOEX;
n 成员wVersion为这个结构体的版本号,在Windows CE7中必须设置为2。
n 成员wObjType指定对象的类型,可取值为:OBJTYPE_DATABASE代表数据库对象,OBJTYPE_RECORD代表记录对象。
n 第3个成员由4个不同的结构体组成的一个联合体,每个结构体分别表示一种不同类型的对象信息,其中CEFILEINFO代表文件对象,CEDIRINFO代表目录对象,CEDBASEINFO代表数据库对象,CERECORDINFO代表数据库内的一个记录对象。这里我们只介绍数据库对象和记录对象,结构体CEDBASEINFO实际上就是结构体CEDBASEINFOEX,它的定义如下:
typedef struct CEDBASEINFOEX {
WORD wVersion;
WORD wNumSortOrder;
DWORD dwFlags;
WCHAR szDbaseName[CEDB_MAXDBASENAMELEN];
DWORD dwDbaseType;
DWORD dwNumRecords;
DWORD dwSize;
FILETIME ftLastModified;
SORTORDERSPECEX rgSortSpecs[CEDB_MAXSORTORDER];
} CEDBASEINFOEX;
n 成员wVersion代表该结构体的版本,必须设置为2。
n 成员wNumSortOrder代表数据库中活动排序字段的个数,在Windows CE7,一次最大可以有32个活动的排序字段。
n 成员dwFlags代表该结构体后面的有效成员,可取值为下面的一个或多个的组合:CEDB_VALIDDBFLAGS,CEDB_VALIDNAME,CEDB_NOCOMPRESS,CEDB_VALIDSORTSPEC, CEDB_VALIDTYPE。默认数据库都是压缩的,如果使用CEDB_NOCOMPRESS标志,那么新创建的数据库将不会进行压缩,这样数据库占用的空间比较大但是读写速度会更快。
n 成员szDbaseName代表新建数据库的名字,最大长度可以为CEDB_MAXDBASENAMELEN,新建数据库时必须指定数据库的名字。
n 成员dwDbaseType代表数据库类型标识。
n 成员dwNumRecords代表数据库的记录个数。
n 成员dwSize和ftLastModified在EDB中没用,为了兼容CEDB版本的结构体。
n 成员rgSortSpecs代表排序字段定义。
结构体CERECORDINFO的定义如下:
typedef struct _CERECORDINFO
{
CEOID oidParent;
} CERECORDINFO;
唯一的成员oidParent代表记录所在的数据库的对象ID (OID)。
获取对象信息成功,函数返回TRUE,失败返回FALSE。
CeOidGetInfoEx2()函数可以返回很多关于对象的有用信息,对于数据库,通过OID可以查询数据库的名称,类型,包含的记录个数等信息。
下面的代码演示如何获取指定数据库包含的记录个数:
CEOIDINFO dbinfo;
int recordsNum;
dbinfo. wVersion = 2;
dbinfo. wObjType = OBJTYPE_DATABASE;
CeOidGetInfoEx2(&ceguid, oidDB, &dbinfo);
recordsNum = dbinfo. infDatabase. dwNumRecords; //获取数据库的记录个数
下面演示如何枚举当前系统中挂载的所有数据库卷以及每个数据库卷中包含的数据库,这个程序的运行结果如图8.1所示。上方的按钮用于开始枚举数据库信息,结果将显示在下面的列表框中。
图8.1 枚举数据库的运行结果
对话框类的头文件为:
// EnumDataBaseEDBDlg.h : header file
//
#pragma once
#include "afxwin.h"
// CEnumDataBaseEDBDlg dialog
class CEnumDataBaseEDBDlg : public CDialog
{
// Construction
public:
CEnumDataBaseEDBDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
enum { IDD = IDD_ENUMDATABASEEDB_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
virtual BOOL OnInitDialog();
#if defined(_DEVICE_RESOLUTION_AWARE) && !defined(WIN32_PLATFORM_WFSP)
afx_msg void OnSize(UINT /*nType*/, int /*cx*/, int /*cy*/);
#endif
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedButtonEnumdb();
//CEdit m_EditDataBases;
CListBox m_ListDataBases;
};
下方按钮的单击事件处理函数为:
void CEnumDataBaseEDBDlg::OnBnClickedButtonEnumdb()
{
// TODO: Add your control notification handler code here
CEGUID ceguid;
TCHAR szVolume[CEDB_MAXDBASENAMELEN];
m_ListDataBases.ResetContent();
CString outDBs = _T("Enum mounted volumes and databases:");
m_ListDataBases.AddString(outDBs);
CREATE_INVALIDGUID(&ceguid);
while(CeEnumDBVolumes(&ceguid, szVolume, sizeof(szVolume)))
{
//输出挂载的数据库卷名称信息
outDBs.Format(_T(" %s:"),szVolume);
m_ListDataBases.AddString(outDBs);
//outDBs.Format(_T("%s\t\t%32s%20s%16s\r\n"),outDBs,_T("DBName"),_T("RecordNum"),_T("DBType"));
//枚举每个挂载卷下的数据库:
HANDLE hDBList;
CEOID oidDB;
//打开数据库查找句柄
hDBList = CeFindFirstDatabaseEx (
&ceguid,
0); //枚举所有类型的数据库
//得到合法的数据库查询句柄
if (hDBList != INVALID_HANDLE_VALUE)
{
oidDB = CeFindNextDatabaseEx (hDBList,&ceguid);
//判断是否达到枚举最后
while (oidDB)
{
//对数据库进行操作
CEOIDINFOEX dbinfo;
int recordsNum;
dbinfo.wVersion = 2;
dbinfo.wObjType = OBJTYPE_DATABASE;
//查询数据库对象信息
CeOidGetInfoEx2(&ceguid, oidDB, &dbinfo);
//获取数据库的记录个数
recordsNum = dbinfo.infDatabase.dwNumRecords;
//分别输出数据库的名称,记录个数,以及数据库类型
outDBs.Format(_T(" %32s%20d%16d"),dbinfo.infDatabase.szDbaseName,recordsNum, dbinfo.infDatabase.dwDbaseType);
m_ListDataBases.AddString(outDBs);
//继续往下枚举数据库
oidDB = CeFindNextDatabaseEx (hDBList,&ceguid);
}
//枚举数据库完毕,关闭数据库查找句柄
CloseHandle (hDBList);
}
else
{
//获取出错原因
if (GetLastError () == ERROR_OUTOFMEMORY)
{
AfxMessageBox(_T("Failed Open DB enum handle: Out of memory"));
}
else
{
AfxMessageBox(_T("Failed Open DB enum handle: Unknown error"));
}
}
//重新初始化挂载卷名称缓冲区
ZeroMemory(szVolume,CEDB_MAXDBASENAMELEN * 2);
}
//m_EditDataBases.SetWindowTextW(outDBs);
}
8.2.5 回写数据库卷
在Windows CE系统中,对挂载数据库卷的更新操作都被缓存在内存中,为了将更新强制回写到永久存储设备上,如果在回写之前,系统重启或断电,那么对数据库卷的更新将丢失。函数CeFlushDBVol()强制回写数据库卷:
BOOL CeFlushDBVol(
PCEGUID pGuid
);
l 参数pGuid为挂载的数据库卷的全局标志CEGUID。这个参数可以设置为NULL,此时将挂载的所有数据库卷都回写。
回写成功,函数将返回TRUE,否则返回FALSE代表回写失败,进一步调用GetLastError()函数获取出错信息,可能的取值为:
n ERROR_NOT_FOUND: 指定的pGuid代表的卷当前没有被挂载。
n ERROR_INVALID_PARAMETER: 如果参数pGuid不为NULL,但是指向一个非法的EDB卷,比如指向一个挂载的CEDB卷。
n ERROR_DISK_FULL: 数据库卷所在的磁盘空间不足,无法进行回写操作。
实际上,当对数据库卷的更新达到足够的量,数据库引擎会定期自动执行回写。为了保证更新的数据被顺利回写,需要应用调用这个函数。但是过于频繁的调用回写,将直接影响数据库的性能,需要在二者之间平衡。
转载于:https://blog.51cto.com/8382359/1342056