OLEDB 提供了静态绑定和动态绑定两种方式,相比动态绑定来说,静态绑定在使用上更加简单,而在灵活性上不如动态绑定,动态绑定在前面已经介绍过了,本文主要介绍OLEDB中的静态,以及常用的数据类型转化接口。
之前的例子都是根据返回的COLUMNINFO结构来知晓数据表中各项的具体信息,然后进行绑定操作,这个操作由于可以动态的针对不同的数据类型绑定为不同的类型,因此称之为动态绑定。动态绑定是建立在我们对数据库中表结构一无所知,而又需要对数据库进行编程,但是一般在实际的项目中开发人员都是知道数据库的具体结构的,而且一旦数据库设计好了后续更改的可能性也不太大,因此可以采取静态绑定的方式来减少编程的复杂度。 在进行静态绑定时,一般针对每个数据库表结构定义一个结构体用来描述表的各项数据,然后利用结构体的偏移来绑定到数据库中。
一般静态绑定需要将数据库表的各项数据与结构体中的成员一一对应,这个时候就涉及到数据库数据类型到C/C++中数据类型的转化,下表列举了常见的数据库类型到C/C++数据类型的转化关系
数据库类型 | OLEDB 类型 | C/C++类型 |
---|---|---|
binary | DBTYPE_BYTES/DBTYPE_IUNKNOWN | BYTE[length]/BLOB |
varbinary | DBTYPE_BYTES/DBTYPE_IUNKNOWN | BLOB |
bit | DBTYPE_BOOL | VARIANT_BOOL |
char | DBTYPE_STR | char[length] |
varchar | DBTYPE_STR | char[length] |
nvarchar | DBTYPE_WSTR | wchar_t[length] |
nchar | DBTYPE_WSTR | wchar_t[length] |
text | DBTYPE_STR/DBTYPE_IUNKNOWN | BLOB |
image | DBTYPE_BYTES/DBTYPE_IUNKNOWN | BLOB |
ntext | DBTYPE_WSTR/DBTYPE_IUNKNOWN | BLOB |
tinyint | DBTYPE_UI1 | BYTE |
smallint | DBTYPE_I2 | SHORT |
int | DBTYPE_I4 | LONG |
bigint | DBTYPE_I8 | LARGE_INTEGER |
real | DBTYPE_R4 | float |
float | DBTYPE_R8 | double |
money | DBTYPE_CY | LARGE_INTEGER |
numeric | DBTYPE_NUMERIC | typedef struct tagDB_NUMERIC { BYTE precision; BYTE scale; BYTE sign; BYTE val[16];} DB_NUMERIC; |
decimal | DBTYPE_NUMERIC | typedef struct tagDB_NUMERIC { BYTE precision; BYTE scale; BYTE sign; BYTE val[16];} DB_NUMERIC; |
sysname | DBTYPE_WSTR | wchar_t[length] |
datetime | DBTYPE_DBTIMESTAMP | typedef struct tagDBTIMESTAMP { SHORT year; USHORT month; USHORT day; USHORT hour; USHORT minute; USHORT second; ULONG fraction;}DBTIMESTAMP; |
timestamp | DBTYPE_BYTES | BYTE[length] |
uniqueidentifier | DBTYPE_GUID | GUID |
下面是一个静态绑定的例子
//静态绑定的结构
typedef struct _tag_DBSTRUCT
{
DBSTATUS dbCodeStatus;
ULONG uCodeLength;
int nCode;
DBSTATUS dbNameStatus;
ULONG uNameLength;
WCHAR szName[NAME_LENGTH];
}DBSTRUCT, *LPDBSTRUCT;
dbBindings[0].bPrecision = 0;
dbBindings[0].bScale = 0;
dbBindings[0].cbMaxLen = sizeof(int);
dbBindings[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
dbBindings[0].dwPart = DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE;
dbBindings[0].iOrdinal = pdbColumnInfo[0].iOrdinal;
dbBindings[0].obStatus = offsetof(DBSTRUCT, dbCodeStatus);
dbBindings[0].obLength = offsetof(DBSTRUCT, uCodeLength);
dbBindings[0].obValue = offsetof(DBSTRUCT, nCode);
dbBindings[0].wType = DBTYPE_I4;
dbBindings[1].bPrecision = 0;
dbBindings[1].bScale = 0;
dbBindings[1].cbMaxLen = sizeof(WCHAR) * NAME_LENGTH;
dbBindings[1].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
dbBindings[1].dwPart = DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE;
dbBindings[1].iOrdinal = pdbColumnInfo[1].iOrdinal;
dbBindings[1].obStatus = offsetof(DBSTRUCT, dbNameStatus);
dbBindings[1].obLength = offsetof(DBSTRUCT, uNameLength);
dbBindings[1].obValue = offsetof(DBSTRUCT, szName);
dbBindings[1].wType = DBTYPE_WSTR;
hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 2, dbBindings, 0, &hAccessor, NULL);
pdbStruct = (DBSTRUCT*)COM_ALLOC(sizeof(DBSTRUCT) * cRows);
while (TRUE)
{
hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, cRows, &cRowsObtained, &prghRows);
if (hRes != S_OK && cRowsObtained == 0)
{
break;
}
ZeroMemory(pdbStruct, sizeof(DBSTRUCT) * cRows);
for (int i = 0; i < cRowsObtained; i++)
{
hRes = pIRowset->GetData(prghRows[i], hAccessor, &pdbStruct[i]);
if (!FAILED(hRes))
{
COM_PRINTF(_T("%012d\t%s\n"), pdbStruct[i].nCode, pdbStruct[i].szName);
}
}
pIRowset->ReleaseRows(cRowsObtained, prghRows, NULL, NULL, NULL);
}
我们针对之前的行政区表来进行演示,在这个表中我们只查询其中的两列数据,与之前的例子相似,针对每列定义3项数据,分别是状态,长度和真实的数据,在绑定的时候就不需要计算总体需要内存的大小,行内存大小就是结构体的大小,在绑定的时候我们结构体成员在结构体中的偏移作为返回数据时各项在缓冲中的偏移。而在访问数据时就需要自己计算偏移,直接使用结构体中的成员即可。 从上面的例子,我总结了静态绑定和动态绑定之间的差别:
数据库中数据类型繁多,而对应到具体的编程语言上有不同的展示方式,具体的语言中对同一种数据库类型有不同的数据类型对应,甚至有的可能并没有什么类型可以直接对应,这就涉及到一个从数据库数据类型到具体编程语言数据类型之间进行转换的问题,针对这一问题OLEDB提供了一个接口——IDataConvert 一般情况下任何数据类型都可以转化为相应格式的字符串,而对应的字符串又可以反过来转化为数据库中相应的数据类型。当然一些特殊转换也是允许的,比如:整型数据间的转换,浮点数间的转换等。这也是使用这个数据转化接口的主要原则。
这个例子相对比较简单,就简单的在之前打印数据库数据中加了一句转化为字符串的操作,然后打印,就不在文中展示了,具体的例子见我放到码云中的代码片段
例子代码: