|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
mysql的prepare其实是本地PHP客户端模拟的,并没有根据你mysql的设置做字符集的调整。应该交与mysqlserver端做prepare,同时得调用mysql_set_character_set去操作,server才会按照字符集去做转义。ado|c++|visual|编程|数据|数据库 ADO是今朝在Windows情况中对照盛行的客户端数据库编程手艺。ADO是创建在OLEDB底层手艺之上的初级编程接口,因此它兼具有壮大的数据处置功效(处置各类分歧范例的数据源、散布式的数据处置等等)和极为复杂、易用的编程接口,因此失掉了普遍的使用。并且按微软公司的企图,OLEDB和ADO将慢慢代替ODBC和DAO。如今先容ADO各类使用的文章和书本有良多,本文侧重站在初学者的角度,扼要切磋一下在VC++中利用ADO编程时的一些成绩。我们但愿浏览本文之前,您对ADO手艺的基础道理有一些懂得。
1、在VC++中利用ADO编程
ADO实践上就是由一组Automation工具组成的组件,因而能够象利用别的任何Automation工具一样利用ADO。ADO中最主要的工具有三个:Connection、Command和Recordset,它们分离暗示毗连工具、命令工具和纪录集工具。假如您熟习利用MFC中的ODBC类(CDatabase、CRecordset)编程,那末进修ADO编程就非常简单了。
利用ADO编程时能够接纳以下三种办法之一:
1、利用预处置指令#import
#import"C:ProgramFilesCommonFilesystemADOmsado15.dll"
no_namespacerename("EOF","EndOfFile")
但要注重不克不及放在stdAfx.h文件的开首,而应当放在一切include指令的前面。不然在编译时会堕落。
程序在编译过程当中,VC++会读出msado15.dll中的范例库信息,主动发生两个该范例库的头文件和完成文件msado15.tlh和msado15.tli(在您的Debug或Release目次下)。在这两个文件里界说了ADO的一切工具和办法,和一些列举型的常量等。我们的程序只需间接挪用这些办法就好了,与利用MFC中的COleDispatchDriver类挪用Automation工具非常相似。
2、利用MFC中的CIDispatchDriver
就是经由过程读取msado15.dll中的范例库信息,创建一个COleDispatchDriver类的派生类,然后经由过程它挪用ADO工具。
3、间接用COM供应的API
如利用以下代码:
CLSIDclsid;
HRESULThr=::CLSIDFromProgID(L"ADODB.Connection",&clsid);
if(FAILED(hr))
{...}
::CoCreateInstance(clsid,NULL,CLSCTX_SERVER,IID_IDispatch,(void**)
&pDispatch);
if(FAILED(hr))
{...}
以上三种办法,第一和第二品种似,大概第一种好用一些,第三种编程大概最贫苦。但大概第三种办法也是效力最高的,程序的尺寸也最小,而且对ADO的把持才能也最强。
据微软材料先容,第一种办法不撑持办法挪用中的默许参数,固然第二种办法也是如许,但第三种就不是如许了。接纳第三种办法的程度也最高。当你必要绕过ADO而间接挪用OLEDB底层的办法时,就必定要利用第三种办法了。
ADO编程的关头,就是纯熟地使用ADO供应的各类工具(object)、办法(method)、属性(property)和容器(collection)。别的,假如是在MSSQL或Oracle等年夜型数据库上编程,还要能纯熟利用SQL言语。
2、利用#import办法的编程步骤
这里倡议您利用#import的办法,由于它易学、易用,代码也对照简便。
1、增加#import指令
翻开stdafx.h文件,将以下内容增加到一切的include指令以后:
#include<icrsint.h>//IncludesupportforVC++Extensions
#import"C:ProgramFilesCommonFilesystemADOmsado15.dll"
no_namespacerename("EOF","adoEOF")
个中icrsint.h文件包括了VC++扩大的一些预处置指令、宏等的界说,用于COM编程时利用。
2、界说_ConnectionPtr型变量,并创建数据库毗连
创建了与数据库服务器的毗连后,才干举行其他有关数据库的会见和操纵。ADO利用Connection工具来创建与数据库服务器的毗连,以是它相称于MFC中的CDatabase类。和CDatabase类一样,挪用Connection工具的Open办法便可创建与服务器的毗连。
数据范例_ConnectionPtr实践上就是由类模板_com_ptr_t而失掉的一个详细的实例类,其界说能够到msado15.tlh、comdef.h和comip.h这三个文件中找到。在msado15.tlh中有:
_COM_SMARTPTR_TYPEDEF(_Collection,__uuidof(_Collection));
经宏扩大后就失掉了_ConnectionPtr类。_ConnectionPtr类封装了Connection工具的Idispatch接口指针,及一些需要的操纵。我们就是经由过程这个指针来利用Connection工具。相似地,前面用到的_CommandPtr和_RecordsetPtr范例也是如许失掉的,它们分离暗示命令工具指针和纪录集工具的指针。
(1)、毗连到MSSQLServer
注重毗连字符串的格局,供应准确的毗连字符串是乐成毗连到数据库服务器的第一步,有干系接字符串的具体信息拜见微软MSDNLibrary光盘。
本例毗连字符串中的server_name,database_name,user_name和password在编程时都应当交换成实践的内容。
_ConnectionPtrpMyConnect=NULL;
HRESULThr=pMyConnect.CreateInstance(__uuidof(Connection)));
if(FAILED(hr))return;
_bstr_tstrConnect="Provider=SQLOLEDB;Server=server_name;"
"Database=database_name;uid=user_name;pwd=password;";
//connectingtothedatabaseservernow:
try{pMyConnect->Open(strConnect,"","",NULL);}
catch(_com_error&e)
{
::MessageBox(NULL,e.Description(),"告诫",MB_OK│MB_ICONWARNING);
}
注重Connection工具的Open办法中的毗连字符串参数必需是BSTR或_bstr_t范例。别的,本例是间接经由过程OLEDBProvider创建毗连,以是无需创建数据源。
(2)、经由过程ODBCDriver毗连到DatabaseServer毗连字符串格局与间接用ODBC编程时的差未几:
_bstr_tstrConnect="DSN=datasource_name;Database=database_name;uid=user_name;pwd=password;";
此时与ODBC编程一样,必需先创建数据源。
3、界说_RecordsetPtr型变量,并翻开数据集
界说_RecordsetPtr型变量,然后经由过程它挪用Recordset工具的Open办法,便可翻开一个数据集。以是Recordset工具与MFC中的CRecordset类相似,它也有以后纪录、以后纪录指针的观点。如:
_RecordsetPtrm_pRecordset;
if(!FAILED(m_pRecordset.CreateInstance(__uuidof(Recordset)))
{
m_pDoc->m_initialized=FALSE;
return;
}
try{
m_pRecordset->Open(_variant_t("mytable"),
_variant_t((IDispatch*)pMyConnect,true),adOpenKeyset,
adLockOptimistic,adCmdTable);
}
catch(_com_error&e)
{
::MessageBox(NULL,"没法翻开mytable表。","提醒",
MB_OK│MB_ICONWARNING);
}
Recordset工具的Open办法十分主要,它的第一个参数能够是一个SQL语句、一个表的名字或一个命令工具等等;第二个参数就是后面创建的毗连工具的指针。别的,用Connection和Command工具的Execute办法也能失掉纪录集,可是只读的。
4、读取以后纪录的数据
我以为读取数据的最便利的办法以下:
try{
m_pRecordset->MoveFirst();
while(m_pRecordset->adoEOF==VARIANT_FALSE)
{
//Retrievecolumnsvalue:
CStringsName=(char*)(_bstr_t)(m_pRecordset->Fields->GetItem
(_variant_t("name"))->Value);
shortcAge=(short)(m_pRecordset->Fields->GetItem
(_variant_t("age"))->Value);
//Dosomethingwhatyouwanttodo:
......
m_pRecordset->MoveNext();
}
}//try
catch(_com_error&e)
{
CStringstr=(char*)e.Description();
::MessageBox(NULL,str+"
又出偏差了。","提醒",
MB_OK│MB_ICONWARNING);
}
本例中的name和age都是字段名,读取的字段值分离保留在sName和cAge变量内。例中的Fields是Recordset工具的容器,GetItem办法前往的是Field工具,而Value则是Field工具的一个属性(即该字段的值)。经由过程此例,应把握利用工具属性的办法。比方,要取得Field工具的Value属性的值能够间接用属性名Value来援用它(如上例),但也能够挪用Get办法,比方:
CStringsName=(char*)(_bstr_t)(m_pRecordset->Fields->GetItem
(_variant_t("name"))->GetValue());
今后例还能够看到,判别是不是抵达纪录集的开端,利用纪录集的adoEOF属性,其值若为真即到了却尾,反之则未到。判别是不是抵达纪录集开首,则可用BOF属性。
别的,读取数据另有一个办法,就是界说一个绑定的类,然后经由过程绑定的变量失掉字段值(详见前面的先容)。
5、修正数据
办法一:
try{
m_pRecordset->MoveFirst();
while(m_pRecordset->adoEOF==VARIANT_FALSE)
{
m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->Value=_bstr_t("赵薇");
......
m_pRecordset->Update();
m_pRecordset->MoveNext();
}
}//try
改动了Value属性的值,即改动了字段的值。
办法二:
m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->PutValue(_bstr_t("赵薇"));
办法三:就是用界说绑定类的办法(详见前面的先容)。
6、增加纪录
新纪录增加乐成后,即主动成为以后纪录。AddNew办法有两种情势,一个含有参数,而另外一个则不带参数。
办法一(不带参数):
//Addnewrecordintothistable:
try{
if(!m_pRecordset->Supports(adAddNew))return;
m_pRecordset->AddNew();
m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->Value=_bstr_t("赵薇");
m_pRecordset->Fields->GetItem
(_variant_t("性别"))->Value=_bstr_t("女");
m_pRecordset->Fields->GetItem
(_variant_t("age"))->Value=_variant_t((short)20);
m_pRecordset->Fields->GetItem
(_variant_t("marry"))->Value=_bstr_t("未婚");
m_pRecordset->Update();
}//try
catch(_com_error&e)
{
::MessageBox(NULL,"又出偏差了。","提醒",MB_OK│MB_ICONWARNING);
}
这类办法弄完了还要挪用Update()。
办法二(带参数):
_variant_tvarName[4],narValue[4];
varName[0]=L"姓名";
varName[1]=L"性别";
varName[2]=L"age";
varName[3]=L"marry";
narValue[0]=_bstr_t("赵薇");
narValue[1]=_bstr_t("女");
narValue[2]=_variant_t((short)20);
narValue[3]=_bstr_t("未婚");
constintnCrit=sizeofvarName/sizeofvarName[0];
//CreateSafeArrayBoundsandinitializethearray
SAFEARRAYBOUNDrgsaName[1],rgsaValue[1];
rgsaName[0].lLbound=0;
rgsaName[0].cElements=nCrit;
SAFEARRAY*psaName=SafeArrayCreate(VT_VARIANT,1,rgsaName);
rgsaValue[0].lLbound=0;
rgsaValue[0].cElements=nCrit;
SAFEARRAY*psaValue=SafeArrayCreate(VT_VARIANT,1,rgsaValue);
//Setthevaluesforeachelementofthearray
HRESULThr1=S_OK.hr2=S_OK;
for(longi=0;i<nCrit&&SUCCEEDED(hr1)&&SUCCEEDED(hr2);i++)
{
hr1=SafeArrayPutElement(psaName,&i,&varName[i]);
hr2=SafeArrayPutElement(psaValue,&i,&narValue[i]);}
//InitializeandfilltheSafeArray
VARIANTvsaName,vsaValue;
vsaName.vt=VT_VARIANT│VT_ARRAY;
vsaValue.vt=VT_VARIANT│VT_ARRAY;
V_ARRAY(&vsaName)=psaName;//&vsaName->parray=psaName;
//seedefinitioninoleauto.hfile.
V_ARRAY(&vsaValue)=psaValue;
//Addanewrecord:
m_pRecordset->AddNew(vsaName,vsaValue);
这类办法不必要挪用Update,由于增加后,ADO会主动挪用它。此办法次要是利用SafeArray挺贫苦。
办法三:就是用界说绑定类的办法(详见前面的先容)。
7、删除纪录
挪用Recordset的Delete办法就好了,删除的是以后纪录。要懂得Delete的别的用法请查阅参考文献。
try{
m_pRecordset->MoveFirst();
while(m_pRecordset->adoEOF==VARIANT_FALSE)
{
CStringsName=(char*)(_bstr_t)(m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->Value);
if(::MessageBox(NULL,"姓名="+sName+"
删除她吗?",
"提醒",MB_YESNO│MB_ICONWARNING)==IDYES)
{
m_pRecordset->Delete(adAffectCurrent);
m_pRecordset->Update();
}
m_pRecordset->MoveNext();
}
}//try
catch(_com_error&e)
{
::MessageBox(NULL,"又出偏差了。","提醒",MB_OK│MB_ICONWARNING);
}
8、利用带参数的命令
Command工具所代表的就是一个Provider可以了解的命令,如SQL语句等。利用Command工具的关头就是把暗示命令的语句设置到CommandText属性中,然后挪用Command工具的Execute办法就好了。一样平常情形下在命令中无需利用参数,但偶然利用参数,能够增添其天真性和效力。
(1).创建毗连、命令工具和纪录集工具
本例中暗示命令的语句就是一个SQL语句(SELECT语句)。SELECT语句中的问号?就代表参数,假如要多个参数,就多放几个问号,每一个问号代表一个参数。
_ConnectionPtrConn1;
_CommandPtrCmd1;
ParametersPtr*Params1=NULL;//Notaninstanceofasmartpointer.
_ParameterPtrParam1;
_RecordsetPtrRs1;
try
{
//CreateConnectionObject(1.5Version)
Conn1.CreateInstance(__uuidof(Connection));
Conn1->ConnectionString=bstrConnect;
Conn1->Open(bstrEmpty,bstrEmpty,bstrEmpty,-1);
//CreateCommandObject
Cmd1.CreateInstance(__uuidof(Command));
Cmd1->ActiveConnection=Conn1;
Cmd1->CommandText=_bstr_t("SELECT*FROMmytableWHEREage<?");
}//try
要注重命令工具必需与毗连工具联系关系起来才干起感化,本例中将命令工具的ActiveConnection属性设置为毗连工具的指针,即为此目标:
Cmd1->ActiveConnection=Conn1;
(2).创立参数工具,并给参数赋值
//CreateParameterObject
Param1=Cmd1->CreateParameter(_bstr_t(bstrEmpty),
adInteger,
adParamInput,
-1,
_variant_t((long)5));
Param1->Value=_variant_t((long)5);
Cmd1->Parameters->Append(Param1);
用命令工具的办法来创立一个参数工具,个中的长度参数(第三个)假如是流动长度的范例,就填-1,假如是字符串等可变长度的就填实在际长度。Parameters是命令工具的一个容器,它的Append办法就是把创立的参数工具追加到该容器里。Append出来的参数按前后按次与SQL语句中的问号从左至右逐一对应。
(3).实行命令翻开纪录集
//OpenRecordsetObject
Rs1=Cmd1->Execute(&vtEmpty,&vtEmpty2,adCmdText);
但要注重,用Command和Connection工具的Execute办法失掉的Recordset是只读的。由于在翻开Recordset之前,我们没法设置它的LockType属性(其默许值为只读)。而在翻开以后设置LockType不起感化。
我发明用上述办法失掉纪录集Rs1后,不仅Rs1中的纪录没法修正,即便间接用SQL语句修正统一表中任何纪录都不可。
要想能修正数据,仍是要用Recordset本人的Open办法才行,如:
try{
m_pRecordset->Open((IDispatch*)Cmd1,vtMissing,
adOpenStatic,adLockOptimistic,adCmdUnspecified);
}
catch(_com_error&e)
{
::MessageBox(NULL,"mytable表不存在。","提醒",MB_OK│MB_ICONWARNING);
}
Recordset工具的Open办法真是太好了,其第一个参数能够是SQL语句、表名字、命令工具指针等等。
9、呼应ADO的关照事务
关照事务就是当某个特定事务产生时,由Provider关照客户程序,换句话说,就是由Provider挪用客户程序中的一个特定的办法(即事务的处置函数)。以是为了呼应一个事务,最关头的就是要完成事务的处置函数。
(1).从ConnectionEventsVt接口派生出一个类
为了呼应_Connection的关照事务,应当从ConnectionEventsVt接口派生出一个类:
classCConnEvent:publicConnectionEventsVt
{
private:
ULONGm_cRef;
public:
CConnEvent(){m_cRef=0;};
~CConnEvent(){};
STDMETHODIMPQueryInterface(REFIIDriid,void**ppv);
STDMETHODIMP_(ULONG)AddRef(void);
STDMETHODIMP_(ULONG)Release(void);
STDMETHODIMPraw_InfoMessage(
structError*pError,
EventStatusEnum*adStatus,
struct_Connection*pConnection);
STDMETHODIMPraw_BeginTransComplete(
LONGTransactionLevel,
structError*pError,
EventStatusEnum*adStatus,
struct_Connection*pConnection);
......
};
(2).完成每个事务的处置函数(但凡带raw_前缀的办法都把它完成了):
STDMETHODIMPCConnEvent::raw_InfoMessage(
structError*pError,
EventStatusEnum*adStatus,
struct_Connection*pConnection)
{
*adStatus=adStatusUnwantedEvent;
returnS_OK;
};
有些办法固然你其实不必要,但也必需完成它,只需复杂地前往一个S_OK便可。但假如要制止常常被挪用,还应在个中将adStatus参数设置为adStatusUnwantedEvent,则在本次挪用后,今后就不会被挪用了。
别的还必需完成QueryInterface,AddRef,和Release三个办法:
STDMETHODIMPCConnEvent::QueryInterface(REFIIDriid,void**ppv)
{
*ppv=NULL;
if(riid==__uuidof(IUnknown)││
riid==__uuidof(ConnectionEventsVt))*ppv=this;
if(*ppv==NULL)
returnResultFromScode(E_NOINTERFACE);
AddRef();
returnNOERROR;
}
STDMETHODIMP_(ULONG)CConnEvent::AddRef(){return++m_cRef;};
STDMETHODIMP_(ULONG)CConnEvent::Release()
{
if(0!=--m_cRef)returnm_cRef;
deletethis;
return0;
}
(3).入手下手呼应关照事务
//StartusingtheConnectionevents
IConnectionPointContainer*pCPC=NULL;
IConnectionPoint*pCP=NULL;
hr=pConn.CreateInstance(__uuidof(Connection));
if(FAILED(hr))return;
hr=pConn->QueryInterface(__uuidof(IConnectionPointContainer),
(void**)&pCPC);
if(FAILED(hr))return;
hr=pCPC->FindConnectionPoint(__uuidof(ConnectionEvents),&pCP);
pCPC->Release();
if(FAILED(hr))return;
pConnEvent=newCConnEvent();
hr=pConnEvent->QueryInterface(__uuidof(IUnknown),(void**)&pUnk);
if(FAILED(hr))returnrc;
hr=pCP->Advise(pUnk,&dwConnEvt);
pCP->Release();
if(FAILED(hr))return;
pConn->Open("dsn=Pubs;","sa","",adConnectUnspecified);
也就是说在毗连(Open)之前就做这些事。
(4).中断呼应关照事务
pConn->Close();
//StopusingtheConnectionevents
hr=pConn->QueryInterface(__uuidof(IConnectionPointContainer),
(void**)&pCPC);
if(FAILED(hr))return;
hr=pCPC->FindConnectionPoint(__uuidof(ConnectionEvents),&pCP);
pCPC->Release();
if(FAILED(hr))returnrc;
hr=pCP->Unadvise(dwConnEvt);
pCP->Release();
if(FAILED(hr))return;
在毗连封闭以后做这件事。每个Rows_log_event中包含event_type,可选值为WRITE_ROWS_EVENT、UPDATE_ROWS_EVENT、DELETE_ROWS_EVENT。从宏名字就能看出用途。 |
|