VC++实战OLEDB编程(九)

在前面的系列文章中,关于OLEDB的大概面貌算是介绍清楚了,很多网友也已经开始了伟大的OLEDB编程实践,这也让我很欣慰。但欣慰之余,我认真的审视了前面的内容,结果发现还是遗漏了很多很多非常非常有用的细节,比如我们常用的数据类型转换,这个专题就几乎没有讨论过,以至于很多网友在OLEDB编程应用中还是只能裹足不前,这也成了一个非常严重的障碍,那么这次我就集中精力先把这个障碍扫清吧。

一般的,我们应用OLEDB都是使用C/C++语言,当然其他语言也是可以的,只要支持COM规范的编程语言,基本都可以使用OLEDB,但是我们知道,对于一些语言来说,DBMS的数据类型是非常丰富的,有些类型必须作必要的转换才能适应语言本身操作的需要,在OLEDB这一层对DBMS可以支持的数据类型定义了一个标准的类型集合如下:

typedef WORD DBTYPE;

enum DBTYPEENUM {

   // The following values exactly match VARENUM

   // in Automation and may be used in VARIANT.

   DBTYPE_EMPTY = 0,

   DBTYPE_NULL = 1,

   DBTYPE_I2 = 2,

   DBTYPE_I4 = 3,

   DBTYPE_R4 = 4,

   DBTYPE_R8 = 5,

   DBTYPE_CY = 6,

   DBTYPE_DATE = 7,

   DBTYPE_BSTR = 8,

   DBTYPE_IDISPATCH = 9,

   DBTYPE_ERROR = 10,

   DBTYPE_BOOL = 11,

   DBTYPE_VARIANT = 12,

   DBTYPE_IUNKNOWN = 13,

   DBTYPE_DECIMAL = 14,

   DBTYPE_UI1 = 17,

   DBTYPE_ARRAY = 0x2000,

   DBTYPE_BYREF = 0x4000,

   DBTYPE_I1 = 16,

   DBTYPE_UI2 = 18,

   DBTYPE_UI4 = 19,

 

   // The following values exactly match VARENUM

   // in Automation but cannot be used in VARIANT.

   DBTYPE_I8 = 20,

   DBTYPE_UI8 = 21,

   DBTYPE_GUID = 72,

   DBTYPE_VECTOR = 0x1000,

   DBTYPE_FILETIME = 64,

   DBTYPE_RESERVED = 0x8000,

 

   // The following values are not in VARENUM in OLE.

   DBTYPE_BYTES = 128,

   DBTYPE_STR = 129,

   DBTYPE_WSTR = 130,

   DBTYPE_NUMERIC = 131,

   DBTYPE_UDT = 132,

   DBTYPE_DBDATE = 133,

   DBTYPE_DBTIME = 134,

   DBTYPE_DBTIMESTAMP = 135,

   DBTYPE_HCHAPTER = 136,

   DBTYPE_PROPVARIANT = 138,

   DBTYPE_VARNUMERIC = 139,

}

抛开上面枚举值的前缀DBTYPE_后面的类型都是些比较熟悉的COM数据类型(有些已经可以直接找到对应的C/C++数据类型),其中我们常用的无非数值型、字符型(字符串)、浮点型,其中整型数值主要是I开头或UI开头带数字的这几类,I的含义就是Intager,而UI 就是unsigned integer ,而数字表示字节长度,1就是一个字节,2就是两个字节,余类推。浮点型主要就是R+数字,R表示real 实数的意思,数字同样也表示字节长度,还有一种浮点数就是DECIMAL型,这种类型可以精确的表示一个有固定小数位数的浮点数,它的内容通常是一个结构,对它的操作就不是很方便了。常用的字符型数据就是那几个带着STR的类型,对他们的访问主要要注意的就是字符的编码方式和存储方式,比如有名的BSTR就是开头放一个4字节整数的长度,然后后面跟着一长串指定长度的字符,WSTR就是UNICODE编码的字符串了,而STR就是普通编码的字符串,通常是MBCS串。

这些类型怎么用呢?这是最关键的问题,当然对他们的使用主要是两块,一个是我们GetColumnInfo的时候,DBCOLUMNINFO结构中的wType字段中就包含了DBMS中这一列的类型信息,这个我们通过程序是无法改变的,只有通过DBMS的DDL语言加以修改,通常一个数据库设计在确定之后,表结构字段类型都是不能再修改的。不管怎样,我们都只认为这个wType是“只读”的。另一方面,我们在创建绑定的时候,需要指定DBBINDING结构中的wType字段。在之前的例子中,都只是简单的让这个字段等于了DBCOLUMNINFO中的wType,其实二者的含义是完全不同的,DBBINDING中的wType其实代表的就是“目标”数据类型,也就是我们想让OLEDB的提供者帮我们把DBCOLUMNINFO中的wType代表的“源”数据类型转换成的类型,很多时候,这个转换是轻松简单的,在我的一个实际项目中,我甚至为除了DBTYPE_IUNKNOWN之外的所有类型都指定了DBTYPE_STR类型,也就是说不管其它类型是什么,统统都转换成容易操作的字符串型数据,这听起来是不是很棒?其实我觉得这也是OLEDB的魅力之一,因为我们用OLEDB的原初目的就是为了处理“一大堆五花八门”的数据,如果没有一个方便的类型转换机制,那么我们编写任何程序都会因为类型转换而变得繁琐异常。下面的例子就演示了如何创建一个全部转换为字符串类型的绑定器:

              for( iCol = 0; iCol < pRData->m_nCols; iCol++ )

              {

                     //将列信息放入索引列表

                     pRData->m_pBindings[iCol].iOrdinal    = pRData->m_pColInfo[iCol].iOrdinal;

                     pRData->m_pBindings[iCol].dwPart     = DBPART_VALUE|DBPART_LENGTH|DBPART_STATUS;

                     pRData->m_pBindings[iCol].obStatus   = dwOffset;

                     pRData->m_pBindings[iCol].obLength  = dwOffset + sizeof(DBSTATUS);

                     pRData->m_pBindings[iCol].obValue    = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG);

                     pRData->m_pBindings[iCol].dwMemOwner = DBMEMOWNER_CLIENTOWNED;

                     pRData->m_pBindings[iCol].eParamIO = DBPARAMIO_NOTPARAM;

                     pRData->m_pBindings[iCol].bPrecision       = pRData->m_pColInfo[iCol].bPrecision;

                     pRData->m_pBindings[iCol].bScale             = pRData->m_pColInfo[iCol].bScale;

                     pRData->m_pBindings[iCol].wType          = DBTYPE_STR;

                     //rgBindings[iCol].wType             = pRData->m_pColInfo[iCol].wType;

                     pRData->m_pBindings[iCol].cbMaxLen       = 0;

                     switch( pRData->m_pColInfo[iCol].wType )

                     {

                     case DBTYPE_NULL:

                     case DBTYPE_EMPTY:

                     case DBTYPE_I1:

                     case DBTYPE_I2:

                     case DBTYPE_I4:

                     case DBTYPE_UI1:

                     case DBTYPE_UI2:

                     case DBTYPE_UI4:

                     case DBTYPE_R4:

                     case DBTYPE_BOOL:

                     case DBTYPE_I8:

                     case DBTYPE_UI8:

                     case DBTYPE_R8:

                     case DBTYPE_CY:

                     case DBTYPE_ERROR:

                            {

                                   pRData->m_pBindings[iCol].cbMaxLen = (25 + 1) * sizeof(CHAR);

                            }                         

                            break;

 

                     case DBTYPE_DECIMAL:

                     case DBTYPE_NUMERIC:

                     case DBTYPE_DATE:

                     case DBTYPE_DBDATE:

                     case DBTYPE_DBTIMESTAMP:

                     case DBTYPE_GUID:

                            {

                                   pRData->m_pBindings[iCol].cbMaxLen = (50 + 1) * sizeof(CHAR);

                            }                         

                            break;

 

                     case DBTYPE_BYTES:

                            {

                                   pRData->m_pBindings[iCol].cbMaxLen =

                                          (pRData->m_pColInfo[iCol].ulColumnSize * 2 + 1) * sizeof(CHAR);

                            }

                            break;

                     case DBTYPE_WSTR:

                     case DBTYPE_BSTR:

                            {

                                   pRData->m_pBindings[iCol].cbMaxLen =

                                          (pRData->m_pColInfo[iCol].ulColumnSize + 1) * sizeof(TCHAR);

                            }

                            break;

                     case DBTYPE_STR:

                            {

                                   pRData->m_pBindings[iCol].cbMaxLen =

                                          (pRData->m_pColInfo[iCol].ulColumnSize + 1) * sizeof(CHAR);

                            }

                            break;

                     default:

                            {

                                   pRData->m_pBindings[iCol].cbMaxLen = MAX_COL_SIZE;

                            }                         

                            break;

                     };

 

                     if( (pRData->m_pColInfo[iCol].wType == DBTYPE_IUNKNOWN ||

                            (pRData->m_pColInfo[iCol].dwFlags & DBCOLUMNFLAGS_ISLONG)) &&

                            (fMultipleObjs || !cStorageObjs) )

                     {

                            pRData->m_pBindings[iCol].pBindExt                = NULL;

                            pRData->m_pBindings[iCol].dwFlags                 = 0;

                            pRData->m_pBindings[iCol].bPrecision              = 0;

                            pRData->m_pBindings[iCol].bScale                    = 0;

                            pRData->m_pBindings[iCol].wType                          = DBTYPE_IUNKNOWN;

                            pRData->m_pBindings[iCol].cbMaxLen                     = 0;

                            pRData->m_pBindings[iCol].pObject                  = GRS_SAFE_ALLOC(g_DBHeap,DBOBJECT*,sizeof(DBOBJECT));

                            pRData->m_pBindings[iCol].pObject->iid            = IID_ISequentialStream;

                            pRData->m_pBindings[iCol].pObject->dwFlags   = STGM_READ;

 

                            cStorageObjs++;

                     }    

 

                     pRData->m_pBindings[iCol].cbMaxLen      

                            = min(pRData->m_pBindings[iCol].cbMaxLen, MAX_COL_SIZE);

                     dwOffset = pRData->m_pBindings[iCol].cbMaxLen + pRData->m_pBindings[iCol].obValue;

                     dwOffset = GRS_UPROUND_8(dwOffset);

              }

从上面的例子看出,几乎为所有的类型都指定了固定的目标类型DBTYPE_STR,而更重要的信息就是后面那个switch 语句,它演示了如何计算每种转换之后需要的内存大小。

说到这里,其实就到了要讨论“元数据”的时候,我不是计算机科班出身的,对“元数据”这种类似星球大战中“原力”这种过度玄幻的说法很是不感冒的,在我看来这个有点故弄玄虚之嫌,其实这里的数据无非两个关键的信息,一个就是数据需要的内存大小,另一个就是对数据内容的解释,理解了这两点,那么对于这个数据类型就可以说是了然于胸了。首先我们来看看内存大小,很多时候,数据库中字段的长度都是固定的,比如INT,CHAR(10)等等这样的类型都是固定长度的,也有一些是不固定长度的,比如Text型 Image型 以及其他的一些“非正常数据类型”,那么在我们GetColumnInfo的时候,DBCOLUMNINFO中的ulColumnSize字段很多时候已经包含了足够的长度信息,当然这个是“元数据”的长度,而我们在创建绑定结构的时候,就需要一个“最大的”长度信息,通常这个是通过DBBINDING的cbMaxLen来指定的,在上面的例子中就很好的演示了如何去计算这个最大长度,很多时候,这个长度大于或等于字段值本身的长度。准备好了长度,那么剩下的就是对内存内容的解释了,在上面的例子中很简单,我们将一切都解释为字符串,那么OLEDB再看到这个绑定信息时就会自动对各种类型的数据做出合乎解释的转换,而不是简单的类型转换,也就是像在C语音中那个sprintf函数做的那样,把很多类型都正确的“解释”成字符串。

到这里似乎这个复杂的数据类型转换问题就轻松的搞定了,真的是这样吗?其实这还只是冰山一角,是最基本的转换而已,而且这个转换隐含了一个问题,就是DBMS支持这样的转换,假如DBMS的OLEDB提供程序不支持这个转换,那么我们就没有办法了,只能用它那个原始的数据类型了(或者你可以在SQL语句中做点手脚),那么如何知道两种类型之间是不是能轻易转换呢?由于篇章的原因我就不在此处粘贴那个巨大的转换表了,大家可以到MSDAC SDK的OLEDB帮助文件中,或者直接在MSDN(最好是2008版)中的索引页面的关键字窗口中输入data types [OLE DB], converting就可以看到那个转换表了,表中明确的指出了那些类型可以转换成那种类型,这个地方你可以做一个收藏,或者直接打印出来当做手册,因为这个是操作数据的基本规则。

显然上面的方法是一个很静态的方法,假如某一天我们遇到了一个新的数据类型,而我们的程序已经发布了,这时候我们不得不为这个新的数据类型指定一个新的转换,并且重新编译程序,做一个升级包,然后让用户去升级,最后才能适应这个新的改变,这听起来都像是一个灾难,那么有没有“动态”的知道两种数据类型是否可以直接转换呢?答案是肯定的,那就是使用IConvertType接口的CanConvert函数,这个接口是Rowsets结果集对象的一个强制接口,也就是说我们只要得到了任意一个Rowsets对象的接口之后,都可以Query出这个接口。因为CanConvert的调用和解释都很简单,大家可以查看MSDN或者查看OLEDB帮助文件中关于这个函数的说明,然后自己试着调用一下这个函数,掌握调用的方法。

最后要特别提示大家的就是,上面的转换不仅只用于GetData操作,也可以同样的用于SetData操作,这才是我们要单独讨论数据类型转换的原初意义。有了这种能力,你甚至可以设计一个程序从用户处接收全部的字符串输入,然后再由OLEDB接口创建一个从字符到任何类型的万能绑定器,绑定之后调用SetData让DBMS自己去把那些字符变成它们在数据库中真实存放的类型,这听起来是不是很棒?当然不要头脑发热,从用户那里接收字符串再转换的方式,最好是能够配上正则表达式校验,否则你会发现你的数据库里怎么会有很多不合逻辑的垃圾?原来聪明的用户,从来不会按要求在一个只能输入电话号码的输入框中输入数字,他们甚至会输入任何他们想象得到的信息,而你的责任就是不允许这样!很多时候用户就是“黑客”!

(未完待续)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值