zoukankan      html  css  js  c++  java
  • ADO

    1 基础    1

    1.1 引入ADO库文件    1

    1.1.1 版本    1

    1.2 初始化OLE/COM库环境    2

    1.3 comdef.h    2

    1.3.1 字符串编码转换    2

    1.3.2 重要的类    3

    1.3.3 重要的变量    4

    1.3.4 智能指针    4

    2 _ConnectionPtr    5

    2.1 连接数据库    5

    2.2 执行SQL语句    5

    2.3 事务处理    6

    2.4 断开连接    7

    3 _RecordsetPtr    8

    3.1 打开记录集    8

    3.1.1 CursorLocation    9

    3.1.2 CursorType    9

    3.1.3 LockType    10

    3.2 遍历记录集    11

    3.3 关闭记录集    12

    3.4 访问BLOB    12

    3.4.1 写入    12

    3.4.2 读取    14

    3.4.3 更新    15

    3.5 书签    15

    3.6 过滤    16

    3.7 Find    16

    3.8 Sort    17

    3.9 Index    17

    3.10 绑定数据    18

    3.10.1 CADORecordBinding派生类    18

    3.10.2 绑定    20

    3.10.3 读取字段    20

    3.10.4 添加新记录    21

    4 _CommandPtr    23

    4.1 执行SQL语句    23

    4.1.1 无名参数    23

    4.1.2 有名参数    23

    4.2 修改BLOB    24

    4.3 执行存储过程    24

    4.3.1 无名参数    25

    4.3.2 有名参数    26

    4.3.3 Refresh    27

    4.3.4 游标位置    27

    4.4 重复使用命令对象    28

    5 ADOX    29

    5.1 引入库文件    29

    5.1.1 一个BUG    29

    5.2 创建数据库    30

    5.3 创建数据表    30

    6 JRO    31

    6.1 VB6.0    31

    6.1.1 引用    31

    6.1.2 代码    31

    6.2 VC++    32

    6.2.1 引入    32

    6.2.2 代码    32

    7 常见问题    33

    7.1 连接失败的原因    33

    7.2 改变当前数据库    33

    7.3 判断某个数据库是否存在    33

    7.4 判断某个表是否存在    34

    7.5 ADO锁定整张表    34

    7.6 获取记录集行数    35

    7.7 解决并发冲突    35

     

    1 基础

    1.1 引入ADO库文件

    VC++使用ADO,首先需要导入ADO的类型库。可以在StdAfx.h里增加如下代码:

    #import "c:/program files/common files/system/ado/msado15.dll" no_namespace rename("EOF","adoEOF")

    no_namespace表示不需要命名空间。删除这个词,则ADO的接口函数将被放在ADODB命名空间内。也可以用rename_namespace("ADO")将命名空间更改为ADO

    rename("EOF","adoEOF")表示将EOF更改为adoEOF。因为EOF在某些文件里被定义为(-1),如此一来,VARIANT_BOOL EOF;就变成了VARIANT_BOOL (-1);把它当做类成员变量编译时就会出错。

    VC++编译器中的预处理器针对这条指令做了哪些工作呢?

    1、根据msado15.dll里的类型库信息生成COM组件的接口文件。具体的就是生成msado15.tlhmsado15.tli,前者是声明文件,后者是实现文件;

    2、替换#import语句为#include "msado15.tlh"。这样,源文件里就可以使用COM组件的接口了;

    3msado15.tliCOM接口的实现文件,如果没有它则程序可以编译但无法连接。不过,msado15.tlimsado15.tlh包含起来了(通过#pragma start_map_region#include),且它的函数全部为内联的。因此,不用再单独编译msado15.tli

    1.1.1 版本

    Windows7 sp1里的msado15.dll,其类型库版本为 6.1,而Windows XPmsado15.dll的版本为2.x。最关键的是新版本的类型库并不是向下兼容的!

    如果在Windows7下编译代码,然后在Windows XP下运行,就有可能会出现问题。解决方法有两个:

    1#import "msado15.dll"中的msado15.dll请使用低版本的,即Windows XP里的msado15.dll

    2、升级Windows XP里的ADO组件。

    1.2 初始化OLE/COM库环境

    ADO是一组COM动态库,所以使用ADO前,必须初始化OLE/COM库。在MFC应用程序里,一个比较好的方法是在应用程序主类的InitInstance成员函数里初始化OLE/COM库。

    BOOL CMyAdoTestApp::InitInstance()

    {

    AfxOleInit();    //这就是初始化COM

    ……

    }

    也可以使用 CoInitializeCoInitializeExOleInitialize初始化COM库,退出程序前请使用CoUninitializeOleUninitialize

    1.3 comdef.h

    msado15.tlh文件里,语句#include <comdef.h>包含了comdef.h头文件,这个头文件里又有如下语句:

    ... ... ...

    #include <comutil.h>

    ... ... ...

    #pragma comment(lib, "comsupp.lib")

    comdef.hcomutil.hcomsupp.lib包含了一些重要的变量、函数、类。

    1.3.1 字符串编码转换

    comutil.h头文件里有两个函数可用于字符串的编码转换:

    namespace _com_util

    {

    BSTR __stdcall ConvertStringToBSTR(const char*pSrc);

    char* __stdcall ConvertBSTRToString(BSTR pSrc);

    }

    _com_util::ConvertStringToBSTRAnsi字符串转换为Unicode字符串,返回值记得要调用SysFreeString释放内存;

    _com_util::ConvertBSTRToString Unicode字符串转换为Ansi字符串,返回值记得要调用 delete[] 释放内存;

    这两个函数的实现代码在comsupp.lib里。

    1.3.2 重要的类

    comutil.hcomdef.h这两个头文件里,有三个重要的类:_bstr_t_variant_t_com_error

    _bstr_t 封装了BSTR_variant_t封装了VARIANT_com_error封装了COM错误。

    使用_bstr_t_variant_t可以极大的简化代码。以下面的代码进行说明:

    _RecordsetPtr m_pRecordset;

    ... ... ...

    _variant_t vAge = m_pRecordset->GetCollect(_T("Age"));

    上面的代码用来读取字段Age。查看msado15.tli里的Recordset15::GetCollect函数,可以知道COM组件返回的其实是一个VARIANTGetCollect函数返回前创建了一个临时_variant_t对象,将VARIANT封装了起来并返回给vAge

    vAge析构时将调用VariantClear销毁COM组件返回的VARIANT。如果Age字段是一个BSTR字符串,VariantClear会调用SysFreeString释放BSTR字符串。

    如果不借助_variant_t,而直接使用VARIANT,会是什么情况呢?那就是必须显式的调用VariantClear函数,销毁COM组件返回的每一个VARIANT。这样代码就会显得非常臃肿,而且一旦疏忽就会发生内存泄露。

    此外,使用_bstr_t也会相当的方便。

    如:代码_bstr_t s(m_pRecordset->GetCollect(_T("Age")));会自动将GetCollect返回的_variant_t转换为BSTR字符串。

    如:可以使用Ansi字符串构造_bstr_t对象

    _bstr_t s1("测试_bstr_t");

    如:可以使用Unicode字符串构造_bstr_t对象

    _bstr_t s2(L"测试_bstr_t");

    如:可以将Ansi字符串或Unicode字符串赋给_bstr_t变量

    s1 = "_bstr_t测试";

    s2 = L"_bstr_t测试";

    如:可以轻松获得Ansi字符串或Unicode字符串

    const char*        sA    =    (const char*)s1;            //返回Ansi字符串

    const wchar_t*    sW    =    (const wchar_t*)s1;        //返回Unicode字符串

    需要注意的是:上面的sAsW所指向的内存由_bstr_t维护。_bstr_t对象s1析构时,sAsW也就成为了野指针。

    1.3.3 重要的变量

    comutil.h里,有全局变量vtMissing,其定义如下:

    extern _variant_t vtMissing;

    它其实是类型为VT_EMPTYVARIANT

    1.3.4 智能指针

    _COM_SMARTPTR_TYPEDEF用来声明智能指针。

    如:智能指针_ConnectionPtr的声明如下:

    _COM_SMARTPTR_TYPEDEF(_Connection, __uuidof(_Connection));

    宏展开之后,其实就是:

    typedef _com_ptr_t< _com_IIID<_Connection, &__uuidof(_Connection)> > _ConnectionPtr;

    _ConnectionPtr的实质就是一个自动维护COM计数的_Connection

    2 _ConnectionPtr

    _ConnectionPtr表示与数据库的连接。

    2.1 连接数据库

    下面的代码将实例化一个_ConnectionPtr,并连接数据库

    HRESULT hr = S_OK;

    _ConnectionPtr m_pConnection;

    {//创建连接

    try

    {

    //创建 COM 对象,__uuidof(Connection) 可以替换为 "ADODB.Connection"

    hr = m_pConnection.CreateInstance(__uuidof(Connection));

    if(SUCCEEDED(hr))

    {//连接 Access 数据库 Demo.mdb

    m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Demo.mdb"

    ,"","",adModeUnknown);

    }

    }

    catch(_com_error& e)

    {

    AfxMessageBox(e.Description());

    }

    } 

    m_pConnection->Open用来连接数据库。第一个参数用来指定连接字符串;第二、三个参数分别为用户名和密码;第四个参数是enum ConnectModeEnum,对数据库的读写进行控制,如:adModeRead表示只读……

    2.2 执行SQL语句

    Execute方法将执行SQL语句,其声明如下:

    _RecordsetPtr Connection15::Execute(_bstr_t CommandText

                            ,VARIANT*RecordsAffected

                            ,long Options);

    CommandText        是命令字串,通常是SQL命令

    RecordsAffected    是操作完成后所影响的行数

    Options                表示CommandText的类型,取值如下

                        adCmdText            表明CommandText是文本

                        adCmdTable            表明CommandText是表名

                        adCmdProc            表明CommandText是存储过程

                        adCmdUnknown        未知

    Execute执行完后返回一个记录集。

    示例代码如下:

    _variant_t RecordsAffected;

    //执行SQL命令,创建表格

    m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText);

    //往表格里面添加记录

    m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) VALUES (1, ''''Washington'''',25,''''1970/1/1'''')",&RecordsAffected,adCmdText);

    //将所有记录old字段的值加一

    m_pConnection->Execute("UPDATE users SET old = old + 1"

    ,&RecordsAffected,adCmdText);

    //执行SQL统计命令得到包含记录条数的记录集

    m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users"

    ,&RecordsAffected,adCmdText);

    //取得第一个字段的值放入vCount变量

    _variant_t vCount = m_pRecordset->GetCollect((_variant_t)((long)0));

    m_pRecordset->Close();//关闭记录集

    CString message;

    message.Format("共有%d条记录",vCount.lVal);

    AfxMessageBox(message);///显示当前记录条数

    2.3 事务处理

    ADO中的事务处理也很简单,只需分别在适当的位置调用Connection对象的三个方法即可,这三个方法是:

    1、在事务开始时调用

    pCnn->BeginTrans();

    2、在事务结束并成功时调用

    pCnn->CommitTrans();

    3、在事务结束并失败时调用

    pCnn->RollbackTrans();

    在使用事务处理时,应尽量减小事务的范围,即减小从事务开始到结束(提交或回滚)之间的时间间隔,以便提高系统效率。需要时也可在调用BeginTrans()方法之前,先设置Connection对象的IsolationLevel属性值,详细内容参见MSDN中有关ADO的技术资料。

    2.4 断开连接

    下面的代码将断开与数据库的连接,并销毁_ConnectionPtr实例

    if(m_pConnection)

    {//关闭ADO连接

    if(m_pConnection->State)

    {

    m_pConnection->Close();

    }

    //下面的语句可有可无。因为m_pConnection是智能指针,析构时会自动Release

    m_pConnection.Release();

    }

    3 _RecordsetPtr

    _RecordsetPtr表示记录集。

    3.1 打开记录集

    使用Open方法打开记录集,其声明如下:

    HRESULT Recordset15::Open(const _variant_t& Source

                                ,const _variant_t & ActiveConnection

                                ,enum CursorTypeEnum CursorType

                                ,enum LockTypeEnum LockType

                                ,long Options);

    Source                数据查询字符串

    ActiveConnection    _Connection*或连接数据库的字符串

    CursorType            光标类型

    LockType            锁定类型

    Options                可以取如下值之一:

                        adCmdText            表明CommandText是文本命令

                        adCmdTable            表明CommandText是表名

                        adCmdProc            表明CommandText是存储过程

                        adCmdUnknown        未知

    下面的代码首先实例化一个_RecordsetPtr,然后在_ConnectionPtr的基础上,打开一个记录集。注意Open函数的第二个参数,表示数据库连接。

    _RecordsetPtr m_pRecordset;

    try

    {

    //创建 COM 对象,__uuidof(Recordset) 可以替换为 "ADODB.Recordset"

    hr = m_pRecordset.CreateInstance(__uuidof(Recordset));

    if(SUCCEEDED(hr))

    {

    m_pRecordset->CursorLocation = adUseClient;

    m_pRecordset->Open(_T("SELECT * FROM DemoTable")

    ,m_pConnection.GetInterfacePtr()

    ,adOpenDynamic

    ,adLockOptimistic //乐观锁

    ,adCmdText);

    }

    }

    catch(_com_error& e)

    {

    AfxMessageBox(e.Description());

    }

    可以将Open函数的第二个参数更换为连接字符串,如:"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Demo.mdb"。在此情况下,Open函数首先连接数据库,然后再打开一个记录集。

    3.1.1 CursorLocation

    CursorLocation表示游标的位置,有两个选项:

    adUseServer    =    2    //默认,表示游标在服务端

    adUseClient        =    3    //表示游标在客户端

    游标在服务端,则记录集对数据变化是敏感的。亦即当其它用户修改了数据库内的数据后,有可能会影响到客户端已经打开的记录集。游标在客户端,就不会有数据敏感性了。

    游标在服务端,则客户端打开记录集时数据将保留在服务端,不会通过网络传给客户端。但是访问记录集里的数据时,就会频繁的网络通讯。游标在客户端,则客户端打开记录集时数据将通过网络缓存到客户端。访问记录集里的数据时,将不再需要网络通讯。因此,如果频繁的访问数据则客户端游标的性能较高。

    此外,服务端游标不支持书签而客户端游标支持书签。这将影响到某些函数的使用,如:GetRecordCount函数有可能返回-1,而不是实际的记录个数。

    3.1.2 CursorType

    游标类型有四种:

    adOpenForwardOnly        =    0,    //向前游标

    adOpenKeyset            =    1,    //键集游标

    adOpenDynamic        =    2,    //动态游标

    adOpenStatic            =    3    //静态游标

    adOpenForwardOnlyadOpenStatic 表示记录集是静态的:打开记录集后,它就不会再发生变化,即使其他用户修改了数据库里的数据。两者的区别在于adOpenForwardOnly只能向前移动游标,而adOpenStatic可以任意移动游标。

    adOpenKeysetadOpenDynamic 表示记录集是动态的:打开记录集后,能够反映其他用户所做的修改。

    adOpenKeyset 的实现原理:打开的记录集仅仅保存记录的关键字(Key),访问某条记录时是根据 Key 到数据库访问的。所以,其他用户修改了某条记录后,再访问这条记录,是能够发现记录改变了。但是,其他用户如果增加、删除记录,并不会更改关键字记录集,因此通过关键字记录集,无法及时获知增加、删除的记录。

    adOpenDynamic 的实现原理:打开的记录集会根据数据库内容的变化及时予以更新。但是需要注意CursorLocation 必须等于 adUseServer,即游标必须在服务端才能实现此功能。

    3.1.3 LockType

    adLockReadOnly        =    1,        //只读

    adLockPessimistic        =    2,         //悲观锁

    adLockOptimistic        =    3,         //乐观锁

    adLockBatchOptimistic    =    4         //批量乐观锁

    悲观锁要求CursorLocation = adUseServer。它表示:编辑字段时就锁定记录,Update之后停止锁定。这种方法最保险,但是效率最低;

    乐观锁只有在 Update 时才锁定记录,更新完毕后停止锁定。也就是说,每调用一次Update,都会锁定、解锁一次;

    批量乐观锁表示调用UpdateBatch时锁定记录,批量更新完毕后停止锁定。也就是说,每调用一次UpdateBatch,都会锁定、解锁一次。

    假定使用悲观锁,多用户执行下面三行代码,会发生什么情况呢?

    m_pRecordset->MoveFirst();

    m_pRecordset->PutCollect("name","Test");

    m_pRecordset->Update();

    用户A执行到m_pRecordset->PutCollect,将锁定记录集的第一行。其它用户运行到m_pRecordset->PutCollect时,也想锁定第一行,但因为已经被A锁定,所以其它用户执行到此行时会等待若干时间后抛出异常。

    用户A执行完m_pRecordset->Update后,将解锁记录集的第一行。注意:具体何时解锁,每种数据库好像不尽相同。经笔者测试,发现Access2000Update后即解锁,而SQL Server2008在记录集关闭后才解锁。

    3.2 遍历记录集

    如下代码将遍历记录集。

    _variant_t var;

    CString sName,sAge;

    try

    {

    //如果 BOF EOF 均为真,则无记录

    if(!m_pRecordset->BOF || !m_pRecordset->adoEOF)

    {

    m_pRecordset->MoveFirst();

    while(!m_pRecordset->adoEOF)

    {

    var = m_pRecordset->GetCollect(_T("Name"));

    if(var.vt != VT_NULL)

    {//姓名

    sName = (LPCTSTR)_bstr_t(var);

    }

    else

    {

    sName.Empty();

    }

    var = m_pRecordset->GetCollect(_T("Age"));

    if(var.vt != VT_NULL)

    {//年龄

    sAge = (LPCTSTR)_bstr_t(var);

    }

    else

    {

    sAge.Empty();

    }

    m_pRecordset->MoveNext();

    }

    }

    }

    catch(_com_error& e)

    {

    AfxMessageBox(e.Description());

    }

    var = m_pRecordset->GetCollect(_T("Name"));的等价代码如下:

    FieldsPtr    pFields    =    m_pRecordset->Fields;

    FieldPtr        pField    =    pFields->Item[_T("Name")];

    var                    =    pField->Value;

    3.3 关闭记录集

    下面的代码将关闭记录集,并销毁_RecordsetPtr实例。

    if(m_pRecordset)

    {//关闭记录集

    if(m_pRecordset->State)

    {

    m_pRecordset->Close();

    }

    //下面的语句可有可无。因为m_pRecordset是智能指针,析构时会自动Release

    m_pRecordset.Release();

    }

    3.4 访问BLOB

    Microsoft SQL中,textimage……被当做二进制数据进行处理。

    可以用Field对象的GetChunkAppendChunk方法来访问。每次可以读出或写入全部数据的一部分,它会记住上次访问的位置。但是如果中间访问了别的字段后,就又得从头来了。

    3.4.1 写入

    可以使用m_pRecordset->PutCollect("data",varBLOB)将二进制数据一次性写入,也可以使用AppendChunk分多次写入二进制数据。

    void CDlgMain::blobFileToDB(LPCTSTR szFile)

    {

    CFile f;

    if(f.Open(szFile,CFile::modeRead | CFile::shareDenyWrite))

    {

    FieldPtr fd = m_pRecordset->Fields->Item["data"];

    DWORD dwSize = f.GetLength();

    if(dwSize > 0)

    {

    const int            nBlock        = 1024;

    DWORD    dwWrite        = 0; //已经写入数据库的字节数

    DWORD            dwAppend    = 0; //单次写入的字节数

    UINT                uRead        = 0;

    SAFEARRAY*    psa        = SafeArrayCreateVector(VT_UI1,0,nBlock);

    _variant_t            vBLOB;

    vBLOB.vt            =    VT_ARRAY | VT_UI1;

    vBLOB.parray        =    psa;

     

    for(;;)

    {

    dwAppend = dwSize - dwWrite;

    if(!dwAppend)

    {

    break;

    }

    if(dwAppend > nBlock)

    {

    dwAppend = nBlock;

    }

    SafeArrayLock(psa);

    uRead = f.Read(psa->pvData,dwAppend);

    SafeArrayUnlock(psa);

    if(uRead != dwAppend)

    {

    break;

    }

    psa->rgsabound[0].cElements = dwAppend;

    fd->AppendChunk(vBLOB);

    dwWrite += dwAppend;

    }

    //SafeArrayDestroy(psa); //vBLOB析构时会自动释放数组 psa

    }

    else

    {

    _variant_t vNull;

    vNull.vt = VT_NULL;

    fd->PutValue(vNull);

    }

    f.Close();

    }

    }

    代码fd->PutValue(vNull);相当于m_pRecordset->PutCollect("data",vNull)用来设置BLOBNULL,也就是清空。也可以使用SQL语句清空某个BLOB字段,如:

    UPDATE 表名 SET BLOB字段名 = NULL WHERE ID=1

    3.4.2 读取

    可以使用m_pRecordset-> GetCollect("data")将二进制数据一次性读取出来,也可以使用GetChunk分多次读取二进制数据。

    void CDlgMain::blobDBtoFile(LPCTSTR szFile)

    {

    CFile f;

    if(f.Open(szFile,CFile::modeWrite | CFile::modeCreate))

    {

    FieldPtr    fd            =    m_pRecordset->Fields->Item["data"];

    long        nSizeActual    =    fd->ActualSize;

    if(nSizeActual > 0)

    {

    const int            nBlock    =    1024;

    long                nRead    =    0;     //已经读取的字节数

    long                nGet    =    0;     //单次读取的字节数

    _variant_t            vBLOB;

    ULONG            uWrite    =    0;     //单次写入文件的字节数

    SAFEARRAY*    psa;

     

    for(;;)

    {

    nGet = nSizeActual - nRead;

    if(!nGet)

    {

    break;

    }

    if(nGet > nBlock)

    {

    nGet = nBlock;

    }

    vBLOB = fd->GetChunk(nGet);

    nRead += nGet;

    if(vBLOB.vt == (VT_ARRAY | VT_UI1)

    && (psa = vBLOB.parray) && psa->cDims == 1)

    {

    uWrite = psa->rgsabound[0].cElements;

    if(uWrite)

    {

    SafeArrayLock(psa);

    f.Write(psa->pvData,uWrite);

    SafeArrayUnlock(psa);

    }

    }

    }

    }

    f.Close();

    }

    }

    3.4.3 更新

    通过_CommandPtr,执行带参数的SQL语句,可实现BLOB数据的修改。请参考_CommandPtr这一章。

    3.5 书签

    书签(bookmark)可以唯一标识记录集中的一个记录,用于快速地将当前记录移回到已访问过的记录,以及进行过滤等等。Provider会自动为记录集中的每一条记录产生一个书签,我们只需要使用它就行了。我们不能试图显示、修改或比较书签。ADO用记录集的Bookmark属性表示当前记录的书签。

    用法步骤:

    rst->Supports(adBookmark);            //判断是否支持书签

    _variant_t VarBookmark;            //建立书签变量

    VarBookmark = rst->Bookmark;        //获得书签值

    ... ... ...                        //可以移动记录

    if(VarBookmark.vt != VT_EMPTY)

    {//将记录位置恢复到书签位置

    rst->Bookmark = VarBookmark;

    }

    3.6 过滤

    Recordset对象的Filter属性表示了当前的过滤条件。它的值可以是以ANDOR连接起来的条件表达式(不含WHERE关键字)、由书签组成的数组或ADO提供的FilterGroupEnum枚举值。为Filter属性设置新值后Recordset的当前记录指针会自动移动到满足过滤条件的第一个记录。例如:

    rst->Filter = _bstr_t ("姓名='赵薇' AND 性别='女'");

    在使用条件表达式时应注意下列问题:

    1、可以用圆括号组成复杂的表达式

    例如:

    rst->Filter = _bstr_t ("(姓名='赵薇' AND 性别='女') OR AGE<25");

    但是微软不允许在括号内用OR,然后在括号外用AND,例如:

    rst->Filter = _bstr_t ("(姓名='赵薇' OR 性别='女') AND AGE<25");

    必须修改为:

    rst->Filter = _bstr_t ("(姓名='赵薇' AND AGE<25) OR (性别='女' AND AGE<25)");

    2、表达式中的比较运算符可以是LIKE

    LIKE后被比较的是一个含有通配符*的字符串,星号表示若干个任意的字符。

    字符串的首部和尾部可以同时带星号*

    rst->Filter = _bstr_t ("姓名 LIKE '**' ");

    也可以只是尾部带星号:

    rst->Filter = _bstr_t ("姓名 LIKE '*' ");

    Filter属性值的类型是Variant,如果过滤条件是由书签组成的数组,则需将该数组转换为SafeArray,然后再封装到一个VARIANT_variant_t型的变量中,再赋给Filter属性。

    3.7 Find

    以下代码用于查找记录

    pRst->Find("姓名 = '赵薇'",1,adSearchForward);

    一般情况下,这种查找是顺序查找,效率较低。可针对某个字段进行排序,其方法如下:

    //将该字段的Optimize属性设置为True

    pRst->Fields->GetItem("姓名")->Properties->

    GetItem("Optimize")->PutValue("True");

    ... ... ...

    pRst->Find("姓名 = '赵薇'",1,adSearchForward);

    ... ... ...

    //将该字段的Optimize属性设置为False

    pRst->Fields->GetItem("姓名")->Properties->

    GetItem("Optimize")->PutValue("False");

    3.8 Sort

    要排序也很简单,只要把要排序的关键字列表设置到Recordset对象的Sort属性里即可,例如:

    pRstAuthors->CursorLocation = adUseClient;

    pRstAuthors->Open("SELECT * FROM mytable"

                        ,_variant_t((IDispatch *)pConnection)

                        ,adOpenStatic,adLockReadOnly, adCmdText);

    ......

    pRst->Sort = "姓名 DESC, 年龄 ASC";

    关键字(即字段名)之间用逗号隔开,如果要以某关键字降序排序,则应在该关键字后加一空格,再加DESC(如上例)。升序时ASC加不加无所谓。本操作是利用索引进行的,并未进行物理排序,所以效率较高。

    但要注意,在打开记录集之前必须将记录集的CursorLocation属性设置为adUseClient,如上例所示。Sort属性值在需要时随时可以修改。

    3.9 Index

    pRst->Index="";            //首先设置索引(数据库里建立的索引)

    pRst->Seek(...,...);        //有序查找

    3.10 绑定数据

    定义一个绑定类,将其成员变量绑定到一个指定的记录集,以方便于访问记录集的字段值。

    3.10.1 CADORecordBinding派生类

    class CCustomRs : public CADORecordBinding

    {

    BEGIN_ADO_BINDING(CCustomRs)

    ADO_VARIABLE_LENGTH_ENTRY2(3

                                                ,adVarChar

                                                ,m_szau_fname

                                                ,sizeof(m_szau_fname)

                                                ,lau_fnameStatus

                                                ,false)

    ADO_VARIABLE_LENGTH_ENTRY2(2

                                                ,adVarChar

                                                ,m_szau_lname

                                                ,sizeof(m_szau_lname)

                                                ,lau_lnameStatus

                                                ,false)

    ADO_VARIABLE_LENGTH_ENTRY2(4

                                                ,adVarChar

                                                ,m_szphone

                                                ,sizeof(m_szphone)

                                                ,lphoneStatus

                                                ,true)

    END_ADO_BINDING()

    public:

    CHAR m_szau_fname[22];

    ULONG lau_fnameStatus;

    CHAR m_szau_lname[42];

    ULONG lau_lnameStatus;

    CHAR m_szphone[14];

    ULONG lphoneStatus;

    };

    其中将要绑定的字段与变量名用BEGIN_ADO_BINDING宏关联起来。每个字段对应于两个变量,一个存放字段的值,另一个存放字段的状态。字段用从1开始的序号表示,如123等等。

    特别要注意的是:如果要绑定的字段是字符串类型,则对应的字符数组的元素个数一定要比字段长度大2(比如m_szau_fname[22],其绑定的字段au_fname的长度实际是20),不这样绑定就会失败。我分析多出的2可能是为了存放字符串结尾的空字符nullBSTR字符串开头的一个字(表示BSTR的长度)。这个问题对于初学者来说可能是一个意想不到的问题。

    CADORecordBinding类的定义在icrsint.h文件里,内容是:

    class CADORecordBinding

    {

    public:

    STDMETHOD_(const ADO_BINDING_ENTRY*, GetADOBindingEntries) (VOID) PURE;

    };

    BEGIN_ADO_BINDING宏的定义也在icrsint.h文件里,内容是:

    #define BEGIN_ADO_BINDING(cls) public: /

    typedef cls ADORowClass; /

    const ADO_BINDING_ENTRY* STDMETHODCALLTYPE GetADOBindingEntries() { /

    static const ADO_BINDING_ENTRY rgADOBindingEntries[] = {

    ADO_VARIABLE_LENGTH_ENTRY2宏的定义也在icrsint.h文件里:

    #define ADO_VARIABLE_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Size, Status, Modify)/

    {Ordinal, /

    DataType, /

    0, /

    0, /

    Size, /

    offsetof(ADORowClass, Buffer), /

    offsetof(ADORowClass, Status), /

    0, /

    classoffset(CADORecordBinding, ADORowClass), /

    Modify},

    #define END_ADO_BINDING宏的定义也在icrsint.h文件里:

    #define END_ADO_BINDING() {0, adEmpty, 0, 0, 0, 0, 0, 0, 0, FALSE}};/

    return rgADOBindingEntries;}

    3.10.2 绑定

    _RecordsetPtr Rs1;

    IADORecordBinding *picRs=NULL;

    CCustomRs rs;

    ......

    Rs1->QueryInterface(__uuidof(IADORecordBinding),(LPVOID*) picRs);

    picRs->BindToRecordset(&rs);

    派生出的类必须通过IADORecordBinding接口才能绑定,调用它的BindToRecordset方法就行了。

    3.10.3 读取字段

    rs中的变量即是当前记录字段的值

    //Set sort and filter condition:

    // Step 4: Manipulate the data

    Rs1->Fields->GetItem("au_lname")->Properties->

    GetItem("Optimize")->Value = true;

    Rs1->Sort = "au_lname ASC";

    Rs1->Filter = "phone LIKE '415 5*'";

    Rs1->MoveFirst();

    while (VARIANT_FALSE == Rs1->EndOfFile)

    {

    printf("Name: %s/t %s/tPhone: %s/n"

    ,(rs.lau_fnameStatus == adFldOK ? rs.m_szau_fname : "")

    ,(rs.lau_lnameStatus == adFldOK ? rs.m_szau_lname : "")

    ,(rs.lphoneStatus == adFldOK ? rs.m_szphone : ""));

    if (rs.lphoneStatus == adFldOK)

    strcpy(rs.m_szphone, "777");

    TESTHR(picRs->Update( // Add change to the batch

    Rs1->MoveNext();

    }

    Rs1->Filter = (long) adFilterNone;

    ......

    if (picRs)

    {

    picRs->Release();

    }

    Rs1->Close();

    pConn->Close();

    只要字段的状态是adFldOK,就可以访问。如果修改了字段,不要忘了先调用picRsUpdate(注意不是RecordsetUpdate),然后才关闭,也不要忘了释放picRs(即picRs->Release();)。

    3.10.4 添加新记录

    此时还可以用IADORecordBinding接口添加新记录

    if(FAILED(picRs->AddNew(&rs)))

    ......

    4 _CommandPtr

    _CommandPtr用来执行SQL语句或调用存储过程。

    4.1 执行SQL语句

    下面的代码首先实例化一个_CommandPtr,然后执行一条SQL语句,执行返回的结果就是一个记录集:

    _CommandPtr cmd;

    cmd.CreateInstance(__uuidof(Command));

    cmd->ActiveConnection    =    theApp.m_pConnection;

    cmd->CommandText        =    "select * from file where name like '%.jpg'";

    _RecordsetPtr    rs        =    cmd->Execute(NULL,NULL,adCmdText);

    _CommandPtr不仅仅能执行SQL语句,它还可以给SQL语句传递参数。

    4.1.1 无名参数

    下面的代码中,SQL语句中的?号就是一个无名参数。执行SQL语句时,从左至右第一个?号将被cmd->Parameters->Item[0]->Value代替;第二个?号将被cmd->Parameters->Item[1]->Value代替……

    所以,调用cmd->Execute执行SQL语句前,需要调用cmd->CreateParameter创建参数,并调用cmd->Parameters->Append将此参数添加至cmd->Parameters集合。

    _CommandPtr cmd;

    cmd.CreateInstance(__uuidof(Command));

    cmd->ActiveConnection    =    theApp.m_pConnection;

    cmd->CommandText        =    "select * from file where name like ?";

    cmd->Parameters->Append(

                    cmd->CreateParameter("",adChar,adParamInput,-1,"%.jpg"));

    _RecordsetPtr    rs        =    cmd->Execute(NULL,NULL,adCmdText);

    4.1.2 有名参数

    下面的代码中,SQL语句中的@name就是一个有名参数。执行SQL语句时,@name将被cmd->Parameters->Item["@name"]->Value代替。

    所以,调用cmd->Execute执行SQL语句前,需要调用cmd->CreateParameter创建有名参数,并调用cmd->Parameters->Append将此参数添加至cmd->Parameters集合。

    _CommandPtr cmd;

    cmd.CreateInstance(__uuidof(Command));

    cmd->ActiveConnection    =    theApp.m_pConnection;

    cmd->CommandText        =    "select * from file where name like @name";

    cmd->Parameters->Append(

                    cmd->CreateParameter("@name",adChar,adParamInput,-1,"%.jpg"));

    _RecordsetPtr    rs        =    cmd->Execute(NULL,NULL,adCmdText);

    4.2 修改BLOB

    通过_CommandPtr,执行带参数的SQL语句,可实现BLOB数据的修改。

    _variant_t vBLOB;

    vBLOB.vt = VT_ARRAY | VT_UI1;

    vBLOB.parray = SafeArrayCreateVector(VT_UI1,0,30);

    SafeArrayLock(vBLOB.parray);

    memset(vBLOB.parray->pvData,0,30);

    SafeArrayUnlock(vBLOB.parray);

     

    _CommandPtr cmd(__uuidof(Command));

    cmd->ActiveConnection = theApp.m_pConnection;

    cmd->CommandText = "UPDATE 表名 SET BLOB字段名=? WHERE ID='1'";

    cmd->Parameters->Append(

    cmd->CreateParameter("",adVarBinary,adParamInput,-1,vBLOB));

    cmd->Execute(NULL,NULL,adCmdText);

    4.3 执行存储过程

    SQL2008里,执行如下SQL语句,创建一个存储过程:

    if exists (select * from sysobjects where id = object_id(N'[sp_1]') and OBJECTPROPERTY(id, N'IsProcedure')= 1)

    drop procedure sp_1

    GO

     

    CREATE PROCEDURE sp_1(@pin1 int

    ,@pin2 CHAR(10)

    ,@pout1 int OUTPUT

    ,@pout2 CHAR(10) OUTPUT)

    AS

    BEGIN

    declare @retval int

    select @pout1 = @pin1 + 100

    select @pout2 = left( ltrim(rtrim(@pin2)) + '123' , 10)

    select * from [file]

    select @retval = 1236

    return @retval

    END

    GO

     

    exec sp_1 10,'Test',20,'789'

    GO

    使用_CommandPtr执行这个存储过程,需要传递、接收的参数如下:

    @RETURN_VALUE(int,返回值)            //0个参数,返回值

    @pin1(int,输入)                            //1个参数

    @pin2(char(10),输入)                    //2个参数

    @pout1(int ,输入/输出)                    //3个参数

    @pout2(char(10),输入/输出)                //4个参数

    4.3.1 无名参数

    不使用Refresh方法,执行存储过程的代码如下:

    _CommandPtr cmd;

    cmd.CreateInstance(__uuidof(Command));

    cmd->ActiveConnection    =    theApp.m_pConnection;

    cmd->CommandText        =    "sp_1"; //存储过程名称

    //添加参数——返回值

    cmd->Parameters->Append(

                cmd->CreateParameter("",adInteger,adParamReturnValue,sizeof(int)));

    //添加参数——@pin1

    cmd->Parameters->Append(

                cmd->CreateParameter("",adInteger,adParamInput,sizeof(int),3L));

    //添加参数——@pin2

    cmd->Parameters->Append(

                cmd->CreateParameter("",adChar,adParamInput,10,_variant_t(_T("DD1"))));

    //添加参数——@pout1

    cmd->Parameters->Append(

                cmd->CreateParameter("",adInteger,adParamOutput,sizeof(int)));

    //添加参数——@pout2

    cmd->Parameters->Append(

                cmd->CreateParameter("",adChar,adParamOutput,10));

    //执行存储过程

    theApp.m_pConnection->CursorLocation = adUseClient;

    _RecordsetPtr    rs    =    cmd->Execute(NULL,NULL,adCmdStoredProc);

    //获取执行结果

    _variant_t vRet        =    cmd->Parameters->Item[0L]->Value; //获得返回值

    _variant_t vin1        =    cmd->Parameters->Item[1L]->Value; //获得@pin1

    _variant_t vin2        =    cmd->Parameters->Item[2L]->Value; //获得@pin2

    _variant_t vout1    =    cmd->Parameters->Item[3L]->Value; //获得@pout1

    _variant_t vout2    =    cmd->Parameters->Item[4L]->Value; //获得@pout2

    总结:不使用Refresh方法,则调用pCmd->Parameters->Append增加参数时,必须要注意参数的顺序。

    4.3.2 有名参数

    如果创建参数时指定参数名称,就可以根据名称获取执行结果了。

    //添加参数——返回值

    cmd->Parameters->Append(

        cmd->CreateParameter("@RETURN_VALUE",adInteger,adParamReturnValue,sizeof(int)));

    //添加参数——@pin1

    cmd->Parameters->Append(

        cmd->CreateParameter("@pin1",adInteger,adParamInput,sizeof(int),3L));

    //添加参数——@pin2

    cmd->Parameters->Append(

        cmd->CreateParameter("@pin2",adChar,adParamInput,10,_variant_t(_T("DD1"))));

    //添加参数——@pout1

    cmd->Parameters->Append(

        cmd->CreateParameter("@pout1",adInteger,adParamOutput,sizeof(int)));

    //添加参数——@pout2

    cmd->Parameters->Append(

        cmd->CreateParameter("@pout2",adChar,adParamOutput,10));

    //执行存储过程

    theApp.m_pConnection->CursorLocation = adUseClient;

    _RecordsetPtr    rs    =    cmd->Execute(NULL,NULL,adCmdStoredProc);

    //获取执行结果

    _variant_t vRet        =    cmd->Parameters->Item["@RETURN_VALUE"]->Value;

    _variant_t vin1        =    cmd->Parameters->Item["@pin1"]->Value;

    _variant_t vin2        =    cmd->Parameters->Item["@pin2"]->Value;

    _variant_t vout1    =    cmd->Parameters->Item["@pout1"]->Value;

    _variant_t vout2    =    cmd->Parameters->Item["@pout2"]->Value;

    注意:即便参数有了名称,添加参数时的顺序也不能改动。

    4.3.3 Refresh

    执行cmd->Parameters->Refresh();会做哪些工作呢?

    1、设置cmd->Parameters->Item[0L]

    设置cmd->Parameters->Item[0L]->Name "@RETURN_VALUE"

    设置cmd->Parameters->Item[0L]->Value VT_EMPTY

    2、设置adParamInput参数的Value VT_EMPTY

    3、设置adParamOutput参数的Value VT_NULL

    当第23、……次执行cmd->Execute前,可以这么做:

    cmd->Parameters->Refresh();

    cmd->Parameters->Item["@pin1"]->Value    =    3L;

    cmd->Parameters->Item["@pin2"]->Value    =    "C";

    cmd->Execute(NULL,NULL,adCmdStoredProc);        //2次执行存储过程

    cmd->Parameters->Refresh();

    cmd->Parameters->Item["@pin1"]->Value    =    4L;

    cmd->Parameters->Item["@pin2"]->Value    =    "D";

    cmd->Execute(NULL,NULL,adCmdStoredProc);         //3次执行存储过程

    4.3.4 游标位置

    如果游标位置不为adUseClient,那么取returnoutput参数之前,必须把返回的记录集关闭掉。

    下面的代码能够正常工作。因为这行代码执行完毕后,返回的记录集会被销毁,销毁前会关闭记录集。

    pCmd->Execute(NULL,NULL,adCmdStoredProc);

    下面的代码就得注意了。在 rs 销毁前,能否取得returnoutput参数,取决于游标位置是否为adUseClient。如果是adUseClient就能正常取值,否则必须关闭rs记录集后,才能正常取值。

    _RecordsetPtr rs = pCmd->Execute(NULL,NULL,adCmdStoredProc);

    theApp.m_pConnection->CursorLocation的取值会影响到pCmd->Execute返回记录集的游标位置。如:pCmd->Execute执行前,执行theApp.m_pConnection->CursorLocation = adUseClient,则返回记录集的游标位置也是adUseClient

    4.4 重复使用命令对象

    一个命令对象如果要重复使用多次(尤其是带参数的命令),则在第一次执行之前,应将它的Prepared属性设置为TRUE。这样会使第一次执行减慢,但却可以使以后的执行全部加快。

     

    5 ADOX

    5.1 引入库文件

    #import "C:/program Files/Common Files/system/ado/msadox.dll"

    5.1.1 一个BUG

    打开文件msadox.tlh,可以看到以下内容。其中,enum DataTypeEnum是先使用,后声明的。

    struct __declspec(uuid("0000061d-0000-0010-8000-00aa006d2ea4"))

    Columns : _Collection

    {

    ... ... ...

    HRESULT Append (

    const _variant_t & Item,

    enum DataTypeEnum Type,

    long DefinedSize );

    ... ... ...

    };

     

    enum DataTypeEnum

    {

    adEmpty = 0,

    adTinyInt = 16,

    ... ... ...

    };

    在此情况下,如下代码将会出现编译错误。

    #import "C:/Program Files/Common Files/System/ado/msado15.dll"

    rename("EOF","adoEOF") no_namespace

    #import "C:/program Files/Common Files/system/ado/msadox.dll"

    原因在于:编译msadox.tlhColumns::Append函数的第2个参数将是msado15.tlh里的enum DataTypeEnum;编译msadox.tliColumns::Append函数的第2个参数却变成了msadox.tlh里的JRO::DataTypeEnum

    解决方法一:调整import的顺序

    #import "C:/program Files/Common Files/system/ado/msadox.dll"

    #import "C:/Program Files/Common Files/System/ado/msado15.dll"

    rename("EOF","adoEOF") no_namespace

    解决方法二:都使用命名空间

    #import "C:/Program Files/Common Files/System/ado/msado15.dll"

    rename("EOF","adoEOF")

    #import "C:/program Files/Common Files/system/ado/msadox.dll"

    5.2 创建数据库

    ADOX::_CatalogPtr pCatalog(__uuidof(ADOX::Catalog));

    _bstr_t s("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=G:\1.mdb");

    pCatalog->Create(s);

    5.3 创建数据表

    ADODB::_ConnectionPtr conn(__uuidof(ADODB::Connection));

    conn->Open(sConn,"","",ADODB::adModeUnknown);

    conn->Execute("CREATE TABLE TestTable(记录编号 INTEGER,姓名 TEXT,年龄 INTEGER)",NULL,ADODB::adCmdText);    

    conn->Close();

    6 JRO

    JROJet and Replication Objects的缩写,它可以用来压缩Access数据库文件。

    6.1 VB6.0

    6.1.1 引用

    6.1.2 代码

    Dim sSrc As String

    Dim sDes As String

    sSrc = "Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Engine Type=5;Data Source=G:dbFile.mdb"

    sDes = "Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Engine Type=5;Data Source=G:1.mdb"

    Dim oJetEngine As New JRO.JetEngine

    oJetEngine.CompactDatabase sSrc, sDes

    Set oJetEngine = Nothing

    注意:"Jet OLEDB:Database Password="可以指定密码。

    6.2 VC++

    6.2.1 引入

    引入JRO需要引入ADO库,代码如下:

    #import "C:/Program Files/Common Files/System/ado/msado15.dll"

    rename("EOF","adoEOF") no_namespace

    #import "C:/Program Files (x86)/Common Files/System/ado/msjro.dll"

    上面的ADO库未使用命名空间,如果使用了命名空间,则代码如下。增加了两条using语句,否则无法完成编译。

    #import "C:/Program Files/Common Files/System/ado/msado15.dll"

    rename("EOF","adoEOF")

    using ADODB::_Recordset;

    using ADODB::_RecordsetPtr;

    #import "C:/Program Files (x86)/Common Files/System/ado/msjro.dll"

    6.2.2 代码

    AfxOleInit();

    _bstr_t sSrc(_T("Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Engine Type=5;Data Source=G:\dbFile.mdb"));

    _bstr_t sDes(_T("Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Engine Type=5;Data Source=G:\1.mdb"));

    JRO::IJetEnginePtr jet(__uuidof(JRO::JetEngine));

    jet->CompactDatabase(sSrc,sDes);

    注意:"Jet OLEDB:Database Password="可以指定密码。

     

    7 常见问题

    7.1 连接失败的原因

    Enterprise Managemer内,打开将服务器的属性对话框,在Security选项卡中,有一个选项Authentication

    如果该选项是Windows NT only,则你的程序所用的连接字符串就一定要包含Trusted_Connection参数,并且其值必须为yes,如:

    "Provider=SQLOLEDB;Server=888;Trusted_Connection=yes"

    ";Database=master;uid=lad;";

    如果不按上述操作,程序运行时连接必然失败。

    如果Authentication选项是SQL Server and Windows NT,则你的程序所用的连接字符串可以不包含Trusted_Connection参数,如:

    "Provider=SQLOLEDB;Server=888;Database=master;uid=lad;pwd=111;";

    因为ADO给该参数取的默认值就是no,所以可以省略。我认为还是取默认值比较安全一些。

    7.2 改变当前数据库

    使用Tansct-SQL中的USE语句即可。

    7.3 判断某个数据库是否存在

    1、可打开master数据库中一个叫做SCHEMATA的视图,其内容列出了该服务器上所有的数据库名称。

    2、更简便的方法是使用USE语句,成功了就存在;不成功,就不存在。例如:

    try

    {

        m_pConnect->Execute("USE INSURANCE_2002",NULL

                            ,adCmdTextadExecuteNoRecords);

    }

    catch(_com_error&e)

    {//数据库INSURANCE_2002不存在

    }

    7.4 判断某个表是否存在

    1、同样判断一个表是否存在,也可以用是否成功地打开它来判断,十分方便,例如:

    try

    {

        m_pRecordset->Open("mytable"

                            ,_variant_t((IDispatch *)m_pConnection,true)

                            ,adOpenKeyset,adLockOptimistic,adCmdTable);

    }

    catch (_com_error &e)

    {//该表不存在

    }

    2、要不然可以采用麻烦一点的办法,就是在MS-SQL服务器上的每个数据库中都有一个名为sysobjects的表,查看此表的内容即知指定的表是否在该数据库中。

    3、同样,每个数据库中都有一个名为TABLES的视图(View),查看此视图的内容即知指定的表是否在该数据库中。

    7.5 ADO锁定整张表

    Dim oConn As New ADODB.Connection

    Dim oRs As New ADODB.Recordset

    oConn.ConnectionTimeout = 15

    oConn.Open 'Provider=SQLOLEDB.1;Password=***;Persist Security Info=True;User ID=***;Initial Catalog=XSSystem;Data Source=10.108.0.1'

    oConn.CommandTimeout = 15

    oConn.IsolationLevel = adXactSerializable

    oConn.BeginTrans

    oRs.CursorLocation = adUseClient

    oRs.Open 'SELECT * FROM ShangYaoGuFenBuyTable with(tablockx) where ID='123' ', oConn, adOpenKeyset, adLockPessimistic

    If oRs.RecordCount > 0 Then

    MsgBox '已经有一条记录了'

    Else

    oRs.AddNew

    oRs('id') = '123'

    oRs.Update

    End If

    oRs.Close

    oConn.CommitTrans '在此步骤之前,ShangYaoGuFenBuyTable整张表会被锁住,其他用户不能进行任何访问

    oConn.Close

    Set oConn = Nothing

    7.6 获取记录集行数

    可以使用SQL语句:select count(*) from 表名

    7.7 解决并发冲突

    使用:Field 对象的 UnderlyingValue OriginalValue 属性;Recordset Resync 方法和 Filter 属性。

    调用UpdateBatch后,一定要立即检查Errors集合是否有错误。如果有错误,则应检查错误是否为并发冲突:

    1、设置 Recordset Filter 属性为adFilterConflictingRecords。若此时 RecordCount 属性等于零,就说明错误是由冲突以外的其他原因引起的。

    2、调用 Recordset Resync 方法,将 AffectRecords 参数设置为adAffectGroup,将 ResyncValues 参数设置为 adResyncUnderlyingValuesResync 方法将用来自基本数据库中的数据刷新在当前 Recordset 对象中的数据。通过使用 adAffectGroup,可以确保只有使用当前筛选设置的情况下可见的记录。

  • 相关阅读:
    Ubuntu下安装了java但启动eclipse报错说没装java
    Servlet之Filter详解
    使用mybatis-generator自动生成model、dao、mapping文件
    深入浅出MyBatis
    彻底理解字符编码
    Java多线程系列
    【Swagger2】解决swagger文档地址请求404的问题
    【Git】Git如何合并某一次commit的内容到指定分支
    【Iterm2】如何解决iterm2窗口自动隐藏的问题
    【Git】.DS_Store 是什么文件
  • 原文地址:https://www.cnblogs.com/hanford/p/6164335.html
Copyright © 2011-2022 走看看