在上一篇中我们简单的介绍了通过Connection对象获取数据,主要目的是为了演示如何连接数据库。在此篇文章中我们将完整的介绍获取数据的各种方法,包括通过Command和Recordset对象获取数据。
获取数据的一个完整步骤是通过Connection对象进行连接,然后使用Command对象发送指令,最后通过Recordset对象来接受数据。下面我们就先来学习如何连接数据库,当然在上一篇中我们已经介绍了如何使用Connection对象连接数据库但是考虑的知识的完整性我还是会在此列出来。
连接数据库
1.使用Connection对象
1: _ConnectionPtr connObject = NULL;
2: connObject.CreateInstance(__uuidof(Connection)); //创建一个实例
3: //连接字符串,就是我们在步骤4中获取的字符串
4: _bstr_t strConnStr = "Provider=SQLOLEDB.1;Password=zw123;\
5: Persist Security Info=True;User ID=sa;Initial Catalog=Communication;Data Source=DAVID-PC";
6: TESTHR(connObject->Open(strConnStr, "", "", adConnectUnspecified));
2.使用Recordset对象隐式连接
1: _RecordsetPtr recordSet = NULL;
2: recordSet.CreateInstance(__uuidof(Recordset));
3: _bstr_t strConnStr = "Provider=SQLOLEDB.1;Password=zw123;\
4: Persist Security Info=True;User ID=sa;Initial Catalog=Communication;Data Source=DAVID-PC";
5: _bstr_t strSql = "SELECT * FROM UserInfo";
6: TESTHR(recordSet->Open(strSql, strConnStr, adOpenStatic, adLockReadOnly, adCmdText));
7: PrintTableHeader(recordSet);
8: PrintTableRecords(recordSet);
在此我们就只有到一个重要的函数Open。由于函数参数比较多,我们还先对这些参数做一个简单的介绍。
函数原型:
HRESULT Open (
const _variant_t & Source,
const _variant_t & ActiveConnection,
enum CursorTypeEnum CursorType,
enum LockTypeEnum LockType,
long Options );
Source:可以是一个有效的Command对象(下面会介绍),SQL语句,表名,存储过程调用,URL,一个文件名或一个存在Recordset对象中的Stream对象。
ActiveConnection:一个Connection对象或者连接字符串。
CursorType:在一个打开的记录集中确定提供着所提供的光标类型。在上一篇文章Recoordset对象的介绍时有介绍,在此不赘述。
LockType:指定记录的锁类型
LockTypeEnum
常量 | 值 | 说明 |
adLockBatchOptimistic | 4 | 指示开放式批更新。需要批更新模式。 |
adLockOptimistic | 3 | 指示逐个记录开放式锁定。提供者使用开放式锁定,仅在调用 Update 方法时锁定记录。 |
adLockPessimistic | 2 | 指示逐个记录保守式锁定。提供者要确保记录编辑成功,通常在编辑之后立即在数据源锁定记录。. |
adLockReadOnly | 1 | 指示只读记录。无法改变数据。 |
adLockUnspecified | -1 | 未指定锁定类型。创建副本时,副本与源对象使用相同的锁定类型。 |
开放式锁定
并行更新数据的一种途径是用开放式锁定。开放式锁定工作原理是通过应用程序来检查数据是否被更新而实现的。一种更普通的实现开放式锁定的方法是在每个表中添加一个“版本列”(version column),对每个表而言,程序每次改变其中一行的时候都会更新这个“版本列”。每个UPDATE语句中的WHERE语句会根据上次查询的结果判断这个版本号是不是被更改了。
保守式锁定
对于开放式锁定来说,另外一种途径是使用保守式锁定。当读取某些行的数据时,他会对这些数据加锁,这样就防止其他的访问这些数据了。具体的实现是需要数据库支持的,然而不幸的是,不是所有的数据库都支持保守式锁定。如果你的数据库支持话,那么你的应用程序直接执行SQL语句来实现保守式锁定将非常容易。
Options 指定命令类型(CommandTypeEnum )和执行选项(ExecuteOptionEnum)
常量 | 值 | 说明 |
adCmdUnspecified | -1 | 不指定命令类型。 |
adCmdText | 1 | 按命令或存储过程调用的文本定义计算CommandText。 |
adCmdTable | 2 | 指定CommandText 为一个表名,内部会组织一个查询所有数据的SQL语句。 |
adCmdStoredProc | 4 | 指定CommandText 为一个存储过程。 |
adCmdUnknown | 8 | · 默认值,表示CommandText为一个未知值,ADO会尝试各种方式去解析文本命令。 |
adCmdFile | 256 | 这个常量只用于Recordset对象中的Open和ReQuery。 |
adCmdTableDirect | 512 | 这个常量只用于Recordset对象中的Open和ReQuery。使用Seek函数时,Recordset对象必须以adCmdTableDirect方式打开,这个值不能和ExecuteOptionEnum中的adAsyncExecute同时使用。 |
常量 | 值 | 说明 |
adAsyncExecute | 0x10 | 表示命令以异步方式执行它不能够跟CommandTypeEnum的adCmdTableDirect一起使用。 |
adAsyncFetch | 0x20 | 超过CacheSize属性所指定的大小后,剩下的数据将以异步的方式获取。 |
adAsyncFetchNonBlocking | 0x40 | 表示主线程绝不阻塞,如果没有检索到数据当前行将被自动移动到文件结束。在一个流对象(Stream )中打开的记录将不会受到adAsyncFetchNonBlocking的影响,这操作将还是会以同步阻塞的方式执行。adAsynchFetchNonBlocking 也不会影响到以adCmdTableDirect打开的记录集 |
adExecuteNoRecords | 0x80 | 表示一个命令或存储过程的执行不返回任何记录集数据,不如像插入数据或更新数据,adExecuteNoRecords 值能做为Command 对象的或者 Connection 对象的Execute方法的参数。 |
adExecuteStream | 0x400 | 表示命令执行的结果流作为一个Stream对象返回。adExecuteStream 选项只能用于Command对象的Excute方法的参数。 |
adExecuteRecord |
| 表示CommandText属性所指定的命令或存储过程只返回一行数据,将其作为一个Record对象。 |
adOptionUnspecified | -1 | 命令未指定。 |
Command对象的使用
使用Command对象我们可以做如下一些事情:
- 定义一个可以执行的命令文本,例如:SQL语句和存储过程。
- 通过使用Parameter对象和Parameters定义参数化的查询和带参的存储过程。
- 通过Execute函数执行一个命令并且返回一个记录集(如果有)。
- 通过使用CommandType属性指定命令的类型,用以优化命令的执行。
- 通过使用Command对象的Dialect属性指定关于命令文本的特殊信息。
- 通过使用Prepared属性控制提供者是否保存一个编译好的版本。
- 通过CommandTimeout设置命令执行的超时值。
- 通过设置Command对象的ActiveConnection属性来关联一个Connection对象。
- 通过Name属性设置一个命名对象并作为Connection的一个方法来调用。
- 为了获取数据,可以设置Recordset对象的Source属性为一个命令对象。
- 传递一个包含命令(例如:一个XML命令)的Stream对象到提供者,前提是提供者要支持它。
1. 创建一个简单(不带参数化的查询或存储过程)的命令。
可以使用3种方式:Connection,Recordset和Command对象,前面我们已经介绍了前两个命名。现在我们再对Command对象进行介绍。
使用一个Command对象时,我们必须指定它的CommandText属性,用以判断执行的命令是什么。然后我们再设置它的命令类型,通过CommandType属性。在执行命令之前我们当然还需要一个Connection对象,我们通过ActiveConnection属性去指定。到现在我们应经把准备工作做好了,只需要调用Execute方法去执行命令了。
1: _bstr_t strConnStr = "Provider=SQLOLEDB.1;Password=zw123;\
2: Persist Security Info=True;User ID=sa;Initial Catalog=Communication;Data Source=DAVID-PC";
3: _ConnectionPtr connObject = NULL;
4: _CommandPtr commandObject = NULL;
5: _RecordsetPtr recordSetObject = NULL;
6:
7: TESTHR(connObject.CreateInstance(__uuidof(Connection)));
8: TESTHR(commandObject.CreateInstance(__uuidof(Command)));
9: TESTHR(recordSetObject.CreateInstance(__uuidof(Recordset)));
10:
11: connObject->Open(strConnStr, "", "", adConnectUnspecified);
12: commandObject->ActiveConnection = connObject;
13: commandObject->CommandText = "SELECT * FROM UserInfo";
14: commandObject->CommandType = adCmdText;
15:
16: recordSetObject = commandObject->Execute(NULL, NULL, adCmdUnspecified);
17:
18: PrintTableHeader(recordSetObject);
19: PrintTableRecords(recordSetObject);
在Execute方法中我们会发现最后一个参数也能够用于指定CommandType当然Execute函数会以传入的参数为准,所以我们在此指定为adCmdUnspecified,这样它就会以我们CommandType中指定的命令类型为准了。更多的命令类型可参见上文中的CommandTypeEnum.
2.带参数的命令对象
带参数的命令对象,这就意味着我们可以传递参数来多次执行命令。在Command对象中有一个叫Prepared的属性,可以帮助我们快速的执行命令,因为只要我们设置Prepared参数为true时,那么我们的命令将会在内存中保留一份编译好的指令,当然如果我们只需要执行一次就没有必要指定PrePared参数为真了。因为指定Prepared他会对命令做一次编译会消耗时间,指定Prepared属性只是为了加速第二次以及以后的执行。
1: _bstr_t strConnStr = "Provider=SQLOLEDB.1;Password=zw123;\
2: Persist Security Info=True;User ID=sa;Initial Catalog=Communication;Data Source=DAVID-PC";
3:
4: _ConnectionPtr connObject = NULL;
5: _CommandPtr commandObject = NULL;
6: _RecordsetPtr recordSetObject = NULL;
7:
8: TESTHR(connObject.CreateInstance(__uuidof(Connection)));
9: TESTHR(commandObject.CreateInstance(__uuidof(Command)));
10: TESTHR(recordSetObject.CreateInstance(__uuidof(Recordset)));
11:
12: connObject->Open(strConnStr, "", "", adConnectUnspecified);
13:
14: commandObject->ActiveConnection = connObject;
15: commandObject->CommandText = "SELECT * FROM UserInfo WHERE UserName = ? OR Sex = ?";
16: commandObject->CommandType = adCmdText;
17: commandObject->Prepared = true;
18:
19: _ParameterPtr param1 = commandObject->CreateParameter("@Name", adVarChar, adParamInput, 6, "Jack");
20: _ParameterPtr param2 = commandObject->CreateParameter("@Sex", adVarChar, adParamInput, 6, "1");
21: commandObject->Parameters->Append(param1);
22: commandObject->Parameters->Append(param2);
23:
24:
25: recordSetObject = commandObject->Execute(NULL, NULL, adCmdText);
26:
27: PrintTableHeader(recordSetObject);
28: PrintTableRecords(recordSetObject);
29:
30: commandObject->Parameters->GetItem("@Name")->Value = "Mary";
31: recordSetObject = commandObject->Execute(NULL, NULL, adCmdText);
32:
33: PrintTableHeader(recordSetObject);
34: PrintTableRecords(recordSetObject);
3.使用命令对象调用存储过程
掉用存储过程,跟上面我们调用带参数的命令对象过程都差不多。只不过我们把CommanType的值设置为了adCmdStoredProc,并且不用设置Prepared的属性为true,因为存储过程是由你的数据提供者创建并编译好的,我们只需要调用就行了。还有一个不同的使用,我们不需要用CreateParameter创建参数对象了,只需要调用Parameters集合中的Refresh方法就能够刷新参数了,当然这个刷新也是建立在消耗性能的基础上的。
在调用存储过程之前我先来创建一个简单的存储过程,我们使用MS Sql Server做为数据源来进行创建。
在Sql Server中新建一个查询,然后输入如下命令,就能创建成功了。
1: CREATE PROCEDURE UserProcedures @UserName nvarchar(10) AS
2: SELECT *FROM UserInfo
3: WHERE UserName = @UserName
下面我们就来看看,如何使用Command对象调用存储过程。
1: //命令对象调用存储过程
2: //删除先前创建的参数对象
3: const int paramCount = commandObject->Parameters->Count;
4: for (int i = 0; i < paramCount; ++i)
5: {
6: commandObject->Parameters->Delete((long)0);
7: }
8:
9: commandObject->CommandText = "UserProcedures";
10: commandObject->CommandType = adCmdStoredProc;
11: commandObject->Parameters->Refresh();
12: commandObject->Parameters->GetItem("@UserName")->Value = "Jack";
13: recordSetObject = commandObject->Execute(NULL, NULL, adCmdUnspecified);
14: PrintTableHeader(recordSetObject);
15: PrintTableRecords(recordSetObject);