在前面我们已经介绍了如何创建一个连接对象(记住叫IDBInitialize,而不是别的什么东西),接下来我们就需要用这个连接对象来创建一个叫做事务的对象了,搞数据库的都知道什么叫事务我就不多说了,这个地方只是强调下一个连接对象可以创建多个事务对象,这一点大家要记清楚,在ATL的OLEDB封装中将连接对象和事务对象被一对一的封装到了一起,统一被称作数据源对象,在一个大型的复杂的数据库应用系统中,我觉得这显然是不够的,必须要针对具体的业务对事务进行一个划分,并为每一类事务创建一个事务对象。
最常用的一个事务对象接口是IOpenRowset,顾名思义,这个接口就是用来打开一个一个结果集的,这个接口的名字就比较好记忆了,同时也比较好理解,下面的例子代码显示了如何从一个数据连接对象创建出一个IOpenRowset接口,注意我这个地方说的是接口,而没有说是对象,例子之后将详细解释这是为什么。
IOpenRowset* pIOpenRowSet = NULL;
IDBCreateSession * pIDBCreateSession = NULL;
//首先从连接对象接口找到等价的CreateSession接口
pIDBInitialize->QueryInterface(
IID_IDBCreateSession, (void**)&pIDBCreateSession));
//创建一个IOpenRowset接口
pIDBCreateSession->CreateSession(
NULL,
IID_IOpenRowset,
(Iunknown**)&pIOpenRowSet));
//与IDBInitialize等价的IDBCreateSession可以释放了,需要时再Query出来就行了
if( pIDBCreateSession )
{
pIDBCreateSession->Release();
}
上面的代码非常简单,其实实际使用中也就这么简单,但是作为一个比较完整的介绍,这个地方我还要告诉大家一些重要的秘密,首先让我们回到例子前的那个问题,就是为什么我说这仅是一个接口,而不说是对象呢?
这是因为,其实一个Session对象是有很多的与IOpenRowset等价的接口来表现的,懂COM的立刻就会知道,这些接口间是可以相互随意QueryInterface的,也就是说拿到了一个接口就等于拿到了其他所有接口,而这些接口全体才完整的表现了Session对象,所以我只说这是一个接口,而不是对象。
其实前面的连接对象的接口IDBInitialize,也是有很多等价的接口的,比如我们在例子中看到的IDBCreateSession接口就是它的一个等价接口,下面我就把分别表现连接对象和事务对象的等价接口族列出来,供大家方便的查询。
首先来看看表现数据连接对象的等价接口族(这个列表来源于MSDN):
CoType TDataSource
{
[mandatory] interface IDBCreateSession;
[mandatory] interface IDBInitialize;
[mandatory] interface IDBProperties;
[mandatory] interface IPersist;
[optional] interface IConnectionPointContainer;
[optional] interface IDBAsynchStatus;
[optional] interface IDBDataSourceAdmin;
[optional] interface IDBInfo;
[optional] interface IPersistFile;
[optional] interface ISupportErrorInfo;
}
Mandatory这个单词的意思是强制的,强制的意思是说,这样的接口是OLEDB提供者必须实现的接口,你调用QueryInterface一定可以查到这些接口,从功能上说,这也定义了一个OLEDB数据源提供者提供的数据连接对象的基本功能。如果这些强制的基本接口都无法顺利的Query到,那么说明这个OLEDB提供者的接口是不完整的,或者说是不符合OLEDB接口规范的。而目前大多数的OLEDB提供程序都已经完整的实现了这些基本的接口。
而相应的optional则说明这些接口是可选的接口,或者说是扩展功能的接口,如果QueryInterface查询不到,这没什么奇怪的,说明OLEDB提供程序没有提供这个扩展的接口。无论怎样在QueryInterface互相查找这些接口时最好判断下返回值,以使程序有较强的容错性。
也许你会很奇怪,为什么一个对象会被划分为这么多复杂的接口呢?其实这也是COM所要求的基本原则,接口的功能越单一越清晰越好,这样无论是接口的实现者,还是接口的调用者都将保持最大的灵活性(因为将来甚至可以很方便的扩展其它接口而不用修改已有的任何代码),同时在某些情况下还可以保持代码的清晰简洁性。比如在我们最开始几个例子中,我们就只用到了IDBInitialize和IDBProperties两个接口,而且代码是非常的清晰简单的,这对一个大型的软件项目来说是非常重要的(我始终认为,大型复杂项目并不意味着代码就一定要复杂,相反无论是代码还是结构等等方面都要保持简单性原则,即越简单越好)。
对于以上的每个接口的详细功能和使用方法,我将在以后的提高部分中详细介绍,这里先简单的例举下,让大家有个映像,关键是要理解一个对象是可以有n个接口的(n >= 1),这对最终理解和使用好OLEDB是至关重要的。
关于等价接口的深层次含义还有一个要注意的问题就是,我们可以创建这些接口中的任意一个作为我们要创建对象的第一个接口,比如,我们可以直接创建IDBProperties接口,再来QueryInterface出IDBInitialize接口,这是绝对被允许的,所以这也是我说这些接口等价的深层原因,其实只要你创建一个对象的任何一个接口,那么这个对象其实都已被成功创建了,而该对象所有的接口其实都变成了可用,因此第一次被创建哪个接口其实完全是一个习惯问题,比如通常的我们创建一个数据连接对象都是先创建IDBInitialize接口,再Query出IDBProperties接口,设置一堆连接属性后,再调用IDBInitialize的Initialize方法连接到数据源,其实这完全可以颠倒过来,我们可以先创建IDBProperties接口,设置一堆连接属性后,再Query出IDBInitialize接口,然后在Initialize连接到一个指定的数据源。下面的例子代码片段将加深你对这一概念的理解:
IDBProperties * pIDBProperties = NULL;
IDBInitialize * pIDBInitialize = NULL;
//1、直接创建IDBProperties接口
CoCreateInstance(CLSID_MSDASQL,
NULL,CLSCTX_INPROC_SERVER,IID_IDBProperties,(void **) &pIDBProperties );
//2、设置连接属性,此处省略了若干步骤,请参看前文的例子
InitProperties[0].dwPropertyID = DBPROP_INIT_DATASOURCE;
InitProperties[0].vValue.vt = VT_BSTR;
InitProperties[0].vValue.bstrVal= SysAllocString(L"(local)");
InitProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;
InitProperties[0].colid = DB_NULLID;
//指定数据库名
InitProperties[1].dwPropertyID = DBPROP_INIT_CATALOG;
InitProperties[1].vValue.vt = VT_BSTR;
InitProperties[1].vValue.bstrVal = SysAllocString(L"MyTest");
InitProperties[1].dwOptions = DBPROPOPTIONS_REQUIRED;
InitProperties[1].colid = DB_NULLID;
//指定身份验证方式为集成安全模式“SSPI”
InitProperties[2].dwPropertyID = DBPROP_AUTH_INTEGRATED;
InitProperties[2].vValue.vt = VT_BSTR;
InitProperties[2].vValue.bstrVal = SysAllocString(L"SSPI");
InitProperties[2].dwOptions = DBPROPOPTIONS_REQUIRED;
InitProperties[2].colid = DB_NULLID;
//创建一个GUID为DBPROPSET_DBINIT的属性集合,这也是初始化连接时需要的唯一一//个属性集合
rgInitPropSet[0].guidPropertySet = DBPROPSET_DBINIT;
rgInitPropSet[0].cProperties = 3;
rgInitPropSet[0].rgProperties = InitProperties;
//3、设置属性
pIDBProperties->SetProperties( 1, (DBPROPSET*)&rgInitPropSet);
CHECK_HR( hr, OpenSessionCleanup );
//4、Query出IDBInitialize接口
pIDBProperties->QueryInterface( IID_IDBInitialize,
(void**) &pIDBInitialize );
//5、调用Initialize连接到数据库
pIDBInitialize->Initialize();
其次让我们再来看看Session对象都有些什么接口吧:
CoType TSession
{
[mandatory] interface IGetDataSource;
[mandatory] interface IOpenRowset;
[mandatory] interface ISessionProperties;
[optional] interface IAlterIndex;
[optional] interface IAlterTable;
[optional] interface IBindResource;
[optional] interface ICreateRow;
[optional] interface IDBCreateCommand;
[optional] interface IDBSchemaRowset;
[optional] interface IIndexDefinition;
[optional] interface ISupportErrorInfo;
[optional] interface ITableCreation;
[optional] interface ITableDefinition;
[optional] interface ITableDefinitionWithConstraints;
[optional] interface ITransaction;
[optional] interface ITransactionJoin;
[optional] interface ITransactionLocal;
[optional] interface ITransactionObject;
}
从表中就可以看出,Session对象其实只有3个强制的基本接口,其他的都是扩展的接口,这里还需要注意的几个接口就是ITransaction(事务)的一系列接口,这些接口对于大型的分布式应用是非常重要的,尤其是ITransactionJoin接口更是分布式事务的关键接口,如果OLEDB提供者没有实现这个接口,那么就说明这个数据源是不支持分布式事务的。
在本章开始的创建简单Session接口的例子中我们通过创建IOpenRowset接口的方法创建了一个Session对象,其它的Session对象接口就可以通过它QueryInterface来得到。
特别要注意的是,Session对象有个IGetDataSource接口,可以通过Session对象找到原初的数据连接对象,有些时候为了代码的简洁性我们可以在创建了IOpenRowset接口之后丢掉之前创建的所有的接口,甚至是数据连接对象的接口,而通过IGetDataSource接口就可以方便的找回数据连接对象的接口,下面的例子代码演示了如何找回数据连接对象的接口:
IGetDataSource* pIGetDataSource = NULL;
IDBInitialize * pIDBInitialize = NULL;
pIOpenRowset->QueryInterface( IID_IGetDataSource,
(void**) &pIGetDataSource );
pIGetDataSource-> GetDataSource (
IID_IDBInitialize,
(IUnknown**)&pIDBInitialize);
像数据连接对象一样,Session对象也是有很多的属性的,这可以通过Query出ISessionProperties再创建一个想要的属性集,然后设置的方法来实现,下面的例子演示了如何设置Session对象的属性:
//注意Session对象只有一个属性集合的一个属性,就是并发级别
//并发级别是控制数据库并发操作的关键在OLEDB中它是一系列值的位或结果
//属性:
SessionProperties[0].dwPropertyID = DBPROP_SESS_AUTOCOMMITISOLEVELS;
SessionProperties[0].vValue.vt = VT_I4;
SessionProperties[0].vValue.lVal= DBPROPVAL_TI_READCOMMITTED;
SessionProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;
SessionProperties[0].colid = DB_NULLID;
//创建一个GUID为DBPROPSET_SESSION的属性集合
//属性集合:
rgSessionPropSet[0].guidPropertySet = DBPROPSET_SESSION;
rgSessionPropSet[0].cProperties = 1;
rgSessionPropSet[0].rgProperties = SessionProperties;
ISessionProperties* pISessionProperties = NULL;
//找到接口
pIOpenRowset->QueryInterface( IID_ISessionProperties,
(void**) &pISessionProperties );
//设置属性
pISessionProperties->SetProperties(1,rgSessionPropSet);
这样Session最关键的属性并发级别就被设定了,这个属性将直接影响后续所有的操作的并发控制级别,当然是指由同一Session对象创建出来的其它后续的对象。
在Session对象的诸多接口中,如果我们要使用SQL语句来操作数据源,那么我们经常使用的一个接口就是IDBCreateCommand这个接口,特别注意的是这个接口是一个可选(optional)的接口,而不是强制的,这是因为OLEDB不但支持连接到传统的数据库使用SQL语句的操作方式,还支持连接到类似网页这样的无结构的数据源的,这也是OLEDB接口较其它数据源接口强大的一个方面,OLEDB的目标就是无论应用系统要使用何种数据(结构化的如数据库,半结构化的如XML,无结构化的如互联网),它都能轻松胜任,而你无需再去寻找别的接口。因此IDBCreateCommand接口就被设置为了一个可选的属性,当然大多数情况下我们都是用OLEDB来处理数据库型的数据源,而这些数据源的大多数都提供了这个接口,因此我们不必担心这个接口不可用,从而导致SQL语言不能用的问题。
Session对象的基础知识就介绍到这,下一回我将继续介绍OLEDB中我们经常应用的焦点:Command对象和Rowset对象。
最常用的一个事务对象接口是IOpenRowset,顾名思义,这个接口就是用来打开一个一个结果集的,这个接口的名字就比较好记忆了,同时也比较好理解,下面的例子代码显示了如何从一个数据连接对象创建出一个IOpenRowset接口,注意我这个地方说的是接口,而没有说是对象,例子之后将详细解释这是为什么。
IOpenRowset* pIOpenRowSet = NULL;
IDBCreateSession * pIDBCreateSession = NULL;
//首先从连接对象接口找到等价的CreateSession接口
pIDBInitialize->QueryInterface(
IID_IDBCreateSession, (void**)&pIDBCreateSession));
//创建一个IOpenRowset接口
pIDBCreateSession->CreateSession(
NULL,
IID_IOpenRowset,
(Iunknown**)&pIOpenRowSet));
//与IDBInitialize等价的IDBCreateSession可以释放了,需要时再Query出来就行了
if( pIDBCreateSession )
{
pIDBCreateSession->Release();
}
上面的代码非常简单,其实实际使用中也就这么简单,但是作为一个比较完整的介绍,这个地方我还要告诉大家一些重要的秘密,首先让我们回到例子前的那个问题,就是为什么我说这仅是一个接口,而不说是对象呢?
这是因为,其实一个Session对象是有很多的与IOpenRowset等价的接口来表现的,懂COM的立刻就会知道,这些接口间是可以相互随意QueryInterface的,也就是说拿到了一个接口就等于拿到了其他所有接口,而这些接口全体才完整的表现了Session对象,所以我只说这是一个接口,而不是对象。
其实前面的连接对象的接口IDBInitialize,也是有很多等价的接口的,比如我们在例子中看到的IDBCreateSession接口就是它的一个等价接口,下面我就把分别表现连接对象和事务对象的等价接口族列出来,供大家方便的查询。
首先来看看表现数据连接对象的等价接口族(这个列表来源于MSDN):
CoType TDataSource
{
[mandatory] interface IDBCreateSession;
[mandatory] interface IDBInitialize;
[mandatory] interface IDBProperties;
[mandatory] interface IPersist;
[optional] interface IConnectionPointContainer;
[optional] interface IDBAsynchStatus;
[optional] interface IDBDataSourceAdmin;
[optional] interface IDBInfo;
[optional] interface IPersistFile;
[optional] interface ISupportErrorInfo;
}
Mandatory这个单词的意思是强制的,强制的意思是说,这样的接口是OLEDB提供者必须实现的接口,你调用QueryInterface一定可以查到这些接口,从功能上说,这也定义了一个OLEDB数据源提供者提供的数据连接对象的基本功能。如果这些强制的基本接口都无法顺利的Query到,那么说明这个OLEDB提供者的接口是不完整的,或者说是不符合OLEDB接口规范的。而目前大多数的OLEDB提供程序都已经完整的实现了这些基本的接口。
而相应的optional则说明这些接口是可选的接口,或者说是扩展功能的接口,如果QueryInterface查询不到,这没什么奇怪的,说明OLEDB提供程序没有提供这个扩展的接口。无论怎样在QueryInterface互相查找这些接口时最好判断下返回值,以使程序有较强的容错性。
也许你会很奇怪,为什么一个对象会被划分为这么多复杂的接口呢?其实这也是COM所要求的基本原则,接口的功能越单一越清晰越好,这样无论是接口的实现者,还是接口的调用者都将保持最大的灵活性(因为将来甚至可以很方便的扩展其它接口而不用修改已有的任何代码),同时在某些情况下还可以保持代码的清晰简洁性。比如在我们最开始几个例子中,我们就只用到了IDBInitialize和IDBProperties两个接口,而且代码是非常的清晰简单的,这对一个大型的软件项目来说是非常重要的(我始终认为,大型复杂项目并不意味着代码就一定要复杂,相反无论是代码还是结构等等方面都要保持简单性原则,即越简单越好)。
对于以上的每个接口的详细功能和使用方法,我将在以后的提高部分中详细介绍,这里先简单的例举下,让大家有个映像,关键是要理解一个对象是可以有n个接口的(n >= 1),这对最终理解和使用好OLEDB是至关重要的。
关于等价接口的深层次含义还有一个要注意的问题就是,我们可以创建这些接口中的任意一个作为我们要创建对象的第一个接口,比如,我们可以直接创建IDBProperties接口,再来QueryInterface出IDBInitialize接口,这是绝对被允许的,所以这也是我说这些接口等价的深层原因,其实只要你创建一个对象的任何一个接口,那么这个对象其实都已被成功创建了,而该对象所有的接口其实都变成了可用,因此第一次被创建哪个接口其实完全是一个习惯问题,比如通常的我们创建一个数据连接对象都是先创建IDBInitialize接口,再Query出IDBProperties接口,设置一堆连接属性后,再调用IDBInitialize的Initialize方法连接到数据源,其实这完全可以颠倒过来,我们可以先创建IDBProperties接口,设置一堆连接属性后,再Query出IDBInitialize接口,然后在Initialize连接到一个指定的数据源。下面的例子代码片段将加深你对这一概念的理解:
IDBProperties * pIDBProperties = NULL;
IDBInitialize * pIDBInitialize = NULL;
//1、直接创建IDBProperties接口
CoCreateInstance(CLSID_MSDASQL,
NULL,CLSCTX_INPROC_SERVER,IID_IDBProperties,(void **) &pIDBProperties );
//2、设置连接属性,此处省略了若干步骤,请参看前文的例子
InitProperties[0].dwPropertyID = DBPROP_INIT_DATASOURCE;
InitProperties[0].vValue.vt = VT_BSTR;
InitProperties[0].vValue.bstrVal= SysAllocString(L"(local)");
InitProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;
InitProperties[0].colid = DB_NULLID;
//指定数据库名
InitProperties[1].dwPropertyID = DBPROP_INIT_CATALOG;
InitProperties[1].vValue.vt = VT_BSTR;
InitProperties[1].vValue.bstrVal = SysAllocString(L"MyTest");
InitProperties[1].dwOptions = DBPROPOPTIONS_REQUIRED;
InitProperties[1].colid = DB_NULLID;
//指定身份验证方式为集成安全模式“SSPI”
InitProperties[2].dwPropertyID = DBPROP_AUTH_INTEGRATED;
InitProperties[2].vValue.vt = VT_BSTR;
InitProperties[2].vValue.bstrVal = SysAllocString(L"SSPI");
InitProperties[2].dwOptions = DBPROPOPTIONS_REQUIRED;
InitProperties[2].colid = DB_NULLID;
//创建一个GUID为DBPROPSET_DBINIT的属性集合,这也是初始化连接时需要的唯一一//个属性集合
rgInitPropSet[0].guidPropertySet = DBPROPSET_DBINIT;
rgInitPropSet[0].cProperties = 3;
rgInitPropSet[0].rgProperties = InitProperties;
//3、设置属性
pIDBProperties->SetProperties( 1, (DBPROPSET*)&rgInitPropSet);
CHECK_HR( hr, OpenSessionCleanup );
//4、Query出IDBInitialize接口
pIDBProperties->QueryInterface( IID_IDBInitialize,
(void**) &pIDBInitialize );
//5、调用Initialize连接到数据库
pIDBInitialize->Initialize();
其次让我们再来看看Session对象都有些什么接口吧:
CoType TSession
{
[mandatory] interface IGetDataSource;
[mandatory] interface IOpenRowset;
[mandatory] interface ISessionProperties;
[optional] interface IAlterIndex;
[optional] interface IAlterTable;
[optional] interface IBindResource;
[optional] interface ICreateRow;
[optional] interface IDBCreateCommand;
[optional] interface IDBSchemaRowset;
[optional] interface IIndexDefinition;
[optional] interface ISupportErrorInfo;
[optional] interface ITableCreation;
[optional] interface ITableDefinition;
[optional] interface ITableDefinitionWithConstraints;
[optional] interface ITransaction;
[optional] interface ITransactionJoin;
[optional] interface ITransactionLocal;
[optional] interface ITransactionObject;
}
从表中就可以看出,Session对象其实只有3个强制的基本接口,其他的都是扩展的接口,这里还需要注意的几个接口就是ITransaction(事务)的一系列接口,这些接口对于大型的分布式应用是非常重要的,尤其是ITransactionJoin接口更是分布式事务的关键接口,如果OLEDB提供者没有实现这个接口,那么就说明这个数据源是不支持分布式事务的。
在本章开始的创建简单Session接口的例子中我们通过创建IOpenRowset接口的方法创建了一个Session对象,其它的Session对象接口就可以通过它QueryInterface来得到。
特别要注意的是,Session对象有个IGetDataSource接口,可以通过Session对象找到原初的数据连接对象,有些时候为了代码的简洁性我们可以在创建了IOpenRowset接口之后丢掉之前创建的所有的接口,甚至是数据连接对象的接口,而通过IGetDataSource接口就可以方便的找回数据连接对象的接口,下面的例子代码演示了如何找回数据连接对象的接口:
IGetDataSource* pIGetDataSource = NULL;
IDBInitialize * pIDBInitialize = NULL;
pIOpenRowset->QueryInterface( IID_IGetDataSource,
(void**) &pIGetDataSource );
pIGetDataSource-> GetDataSource (
IID_IDBInitialize,
(IUnknown**)&pIDBInitialize);
像数据连接对象一样,Session对象也是有很多的属性的,这可以通过Query出ISessionProperties再创建一个想要的属性集,然后设置的方法来实现,下面的例子演示了如何设置Session对象的属性:
//注意Session对象只有一个属性集合的一个属性,就是并发级别
//并发级别是控制数据库并发操作的关键在OLEDB中它是一系列值的位或结果
//属性:
SessionProperties[0].dwPropertyID = DBPROP_SESS_AUTOCOMMITISOLEVELS;
SessionProperties[0].vValue.vt = VT_I4;
SessionProperties[0].vValue.lVal= DBPROPVAL_TI_READCOMMITTED;
SessionProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;
SessionProperties[0].colid = DB_NULLID;
//创建一个GUID为DBPROPSET_SESSION的属性集合
//属性集合:
rgSessionPropSet[0].guidPropertySet = DBPROPSET_SESSION;
rgSessionPropSet[0].cProperties = 1;
rgSessionPropSet[0].rgProperties = SessionProperties;
ISessionProperties* pISessionProperties = NULL;
//找到接口
pIOpenRowset->QueryInterface( IID_ISessionProperties,
(void**) &pISessionProperties );
//设置属性
pISessionProperties->SetProperties(1,rgSessionPropSet);
这样Session最关键的属性并发级别就被设定了,这个属性将直接影响后续所有的操作的并发控制级别,当然是指由同一Session对象创建出来的其它后续的对象。
在Session对象的诸多接口中,如果我们要使用SQL语句来操作数据源,那么我们经常使用的一个接口就是IDBCreateCommand这个接口,特别注意的是这个接口是一个可选(optional)的接口,而不是强制的,这是因为OLEDB不但支持连接到传统的数据库使用SQL语句的操作方式,还支持连接到类似网页这样的无结构的数据源的,这也是OLEDB接口较其它数据源接口强大的一个方面,OLEDB的目标就是无论应用系统要使用何种数据(结构化的如数据库,半结构化的如XML,无结构化的如互联网),它都能轻松胜任,而你无需再去寻找别的接口。因此IDBCreateCommand接口就被设置为了一个可选的属性,当然大多数情况下我们都是用OLEDB来处理数据库型的数据源,而这些数据源的大多数都提供了这个接口,因此我们不必担心这个接口不可用,从而导致SQL语言不能用的问题。
Session对象的基础知识就介绍到这,下一回我将继续介绍OLEDB中我们经常应用的焦点:Command对象和Rowset对象。