WrapSP - SQL 存储过程的包装类生成器
一个简单易用的工具,用于生成基于 ADO 的类,以调用 SQL 存储过程。只需点击几下,您就可以访问数据库中的任何存储过程。
引言
WrapSP (Wrapper for Stored Procedures) 是一个简化创建用于调用 SQL 存储过程 (SP) 包装器类的工具。只需点击几下,您就可以获得每个 SP 的一个类,只需选择 SP,然后让 WrapSP 为您完成工作。
WrapSP 分析所选存储过程的输入/输出参数和结果集,并生成一个完美定制的类来调用它。如果您必须重新设计存储过程,那么您可以在几秒钟内重新创建包装器类。除了该类之外,您总是会得到一个如何使用它的工作示例,只需将其复制并粘贴到您的项目中即可。
无论您需要调用几十个存储过程还是只有一个,WrapSP 都会为您节省工作。
背景
如您所知,存储过程是使用 SQL 语言编写的脚本。它存储在数据库中,可以从另一个 SQL 脚本或任何连接到数据库的程序调用。存储过程通常用作程序和数据库之间的接口,因为它们将用 SQL 语言编写的数据库逻辑与用(例如)C++ 编写的程序解耦。
从调用程序角度来看,存储过程可以具有以下项或它们的任意组合
RETURN_VALUE
INPUT
- 参数OUTPUT
- 参数- 结果集
RETURN_VALUE
是一个整数,将像在 C 程序中一样用于传递一些结果(主要是错误代码)。
INPUT
和 OUTPUT
参数是可选的。它们将用于向存储过程传递任何类型的单个值或从存储过程获取任何单个值。
结果集是数据库表形式的结构,包含任何类型的列和行。它将用于从数据库获取任意数量的任何信息。通常,它是带有某些 JOIN
等的 SELECT
语句的结果。
C++ 程序中传递给数据库或从数据库接收的所有数据都必须存储在 VARIANT
记录中。这使得数据库编程有点复杂。要调用存储过程,您必须首先找出必须传递哪些参数。然后,您必须找出应该预期哪些结果。如果存储过程很复杂,那么传递正确的参数并接收正确的值集可能会很困难。通常,有必要更改存储过程,然后您必须从头开始工作。
所有这些例行工作都可以使用 WrapSP 自动化和简化。在我的项目中,我使用它节省了大量时间。对现有存储过程的任何更改都不再是噩梦。
如何使用 WrapSP 生成存储过程包装器类
让我逐步描述如何使用 WrapSP。您调用程序 (WrapSP.exe) 并获得以下窗口
- 首先,您必须登录到所需的数据库。从 Data Base 组合框中选择适当的数据库。在 Log ID 和 Password 字段中,输入登录数据。然后,按下 Connect To DB 按钮。将建立与数据库的连接。如果您输入错误的登录数据或 ODBC 连接未预定义,则会收到错误消息。
- 成功连接到数据库后,Stored Procedure 组合框将启用,您可以选择要为其创建包装器类的存储过程。
- 现在,您已准备好为所选存储过程创建包装器类。您按下 Create Wrapper Class 按钮,将出现以下对话框
- 现在,您按下 Create Class 按钮,头文件和源文件将生成到指定的目标目录中。如果这些文件已经存在,将询问您是否要覆盖它们。
所选存储过程的所有参数信息将显示在 SP Parameter 列表中。如果此存储过程没有任何参数,则列表中将只显示默认参数 RETURN_VALUE
。
在第二个列表 Returned Recordset 中,将显示返回记录集的结构。如果存储过程不提供任何结果集,则此列表将为空。
如您所见,您可以为生成的类指定目标路径。类名以及生成的头文件和源文件的名称将使用所选存储过程的名称自动生成。
在底部,有两个选项,我称之为 Parameter by Index 和 Parameter by Name。这些选项指定存储过程的参数将如何在包装器类中应用。
如果选择 By Index,则只生成一个函数 SetParam
,并且参数将使用存储过程参数的名称作为索引(例如:“Beginning_Date”)应用。这是推荐的选择,因为它会生成更少的代码。
如果选择 By Name,则对于每个 INPUT
和 OUTPUT
参数,将生成一个单独的函数,其名称为该参数的名称(例如:“Set_Beginning_Date”)。
一个好的做法是有一个单独的测试项目,您可以在其中首先测试生成的类,然后再将其添加到自己的项目中。为此,我创建了一个子项目 TestIt,因此我的目标路径设置为此项目。
使用生成的包装器类
要在您的项目中使用生成的类,您必须遵循以下步骤
- 为了测试类,建议创建一个简单的测试项目。
- 您为要调用的每个存储过程生成一个包装器类。
- 您将生成的包装器类的头文件和源文件插入到您的测试项目中。
- 您使用生成的测试函数测试存储过程的调用。
- 如果需要更改存储过程的代码,请执行此操作,然后再次为存储过程生成包装器类。您可以重复此操作,直到对结果满意为止。
- 如果您对结果满意,可以将测试过的类纳入您的最终项目。
让我们看以下实际示例
出于测试目的,我生成了一个简单的基于命令行的项目 (TestIt)。您也可以将其用于您的目的。由于生成的类是基于 ADO 的,因此您必须插入对 ADO 的引用。一个好的位置是您项目目录中的 STDAFX.H 文件。插入应如下所示
//ADO Stuff manually inserted
#pragma warning(disable:4146)
#import "c:\program files\common files\system\ado\msado15.dll"
named_guids rename("EOF", "bEOF") rename("BOF", "bBOF")
#pragma warning(default:4146)
using namespace ADODB;
inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);};
//ADO Stuff manually inserted
如您所见,还定义了一个函数 TESTHR
,它将在生成的类中的某些地方使用。ADO DLL 在您 PC 上的存储位置可能与上述位置不同,因此您可能需要在您的情况下进行调整。
在您的项目文件中,您必须插入以下头文件
#include "stdafx.h"
#include "adodatabase.h" // base class
#include "EmployeeSalesbyCountry.h" // generated Wrapper Class 1
#include "TenMostExpensiveProducts.h" // generated Wrapper Class 2
AdoDatabase.h 是所有生成的存储过程类的基类,因此它必须始终插入到您的项目中。相应的源文件 AdoDatabase.cpp 也必须是您项目的一部分。
对于每个生成的包装器类,您还必须将头文件和源文件插入到您的项目中(我在此处为两个类完成了此操作)。
您可以将生成的测试函数定义为外部函数
void extern Test_CEmployeeSalesbyCountry();
void extern Test_CTenMostExpensiveProducts();
然后,您可以在测试项目中简单地调用这些函数,如下所示
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// some error handling ...
}
else
{ // call the Test Functions
Test_CEmployeeSalesbyCountry();
Test_CTenMostExpensiveProducts();
}
return nRetCode;
}
测试函数将始终插入到其类的相应源文件的末尾,因此对于每个生成的包装器类,您将获得一个专门的测试函数。让我们看其中一个函数。Test_CEmployeeSalesbyCountry
看起来像这样(您所看到的就是您从 WrapSP 获得的准确内容)
/*----------------------------------------------------------------*/
/* Example for using the created class. */
/*----------------------------------------------------------------*/
void Test_CEmployeeSalesbyCountry()
{
CEmployeeSalesbyCountry usp;
try {
// Open the DB and initialize evrything
if (usp.Connect()) {
// TODO: Add your initialization for the input parameters !!!
_variant_t vIn;
usp.SetParam( _T("Beginning_Date"), vIn); // datetime
usp.SetParam( _T("Ending_Date"), vIn); // datetime
// Call the SP and check the correct return code if supplied
if (0 == usp.Call()) {
// TODO: Loop through the returned records
// and do something useful with them !!!
EmployeeSalesbyCountry_RecordSet_Type rRec;
_variant_t vRec;
while (usp.GetNextRecordSet(rRec)) {
vRec = rRec.vCountry; // VarWChar(15)
vRec = rRec.vLastName; // VarWChar(20)
vRec = rRec.vFirstName; // VarWChar(10)
vRec = rRec.vShippedDate; // DBTimeStamp
vRec = rRec.vOrderID; // Integer
vRec = rRec.vSaleAmount; // Currency
}
}
}
}
catch(_com_error e) {
CADODatabase::Msg(e.ErrorMessage());
}
}
如您所见,将声明生成类 (usp
) 的一个实例。然后,通过调用 usp.Connect()
建立与数据库的连接。数据库连接的参数在类内部定义,因此此处无需指定它们。将声明存储过程的所有输入/输出参数。输入参数必须设置为其正确的值。然后,可以进行存储过程调用:usp.Call()
。调用存储过程后,我们要么读取输出参数(这也将生成),要么像此示例中一样,我们通过可以循环遍历的结果集获取结果。为了简化对结果集的访问,将始终生成适当的记录类型(此处为:EmployeeSalesbyCountry_RecordSet_Type
)。
您可以将测试函数剪切并粘贴到您的项目中。但是,如果您必须为存储过程提供一些输入参数,则必须用一些实际值替换生成的占位符 (vIn
);否则,您将从存储过程获得空结果集。因此,为了测试,我们将空的 vIn
参数替换为如下内容
usp.SetParam( _T("Beginning_Date"),
vIn = COleDateTime(1996,8,1,0,0,0)); // datetime
usp.SetParam( _T("Ending_Date"),
vIn = COleDateTime(1996,9,1,0,0,0)); // datetime
现在,如果您执行测试函数并在调试模式下逐步执行它,那么您将看到在成功调用存储过程后,您可以循环遍历返回的记录集并读取记录的元素。
这就是我们需要的!在测试完存储过程调用后,您可以复制这段代码并将其粘贴到您的项目中。
关注点
本文的重点是如何在您自己的项目中使用 WrapSP 工具和生成的类。我没有解释工具本身的内部结构;代码非常简单,因此您可以自己分析它。
最后,我想总结一些对我来说很重要的提示。如果您使用 WrapSP,它们可能也会对您有所帮助。
- 确保您要使用的数据库的 ODBC 连接已正确定义并正常工作。
- 我建议您使用一个测试项目在简单的环境中检查您的类,然后再将其应用于更复杂的项目。
- WrapSP 为返回的记录集生成一个
RecordSet
类型。因此,您可以轻松访问其中的各个元素。 - 对于每个存储过程参数和结果集的每个元素,将生成一个描述您必须提供或将收到的数据类型的注释。这将简化您的编码。
- 也许您正在使用的一些存储过程非常复杂,以至于 WrapSP 无法确定结果集。在这种情况下,您可以暂时注释掉存储过程的内部代码,除了您用于返回值的那最后一个
SELECT
,然后再次运行 WrapSP。对于 WrapSP 来说,重要的是识别结果集的结构,而不是返回的值本身。 - 如果您有根据输入参数提供不同结构返回集的存储过程,您可以为每个返回结构创建一个单独的包装器类。但为了避免命名冲突,您必须手动重命名它们。
- 如果您的代码中使用了多个包装器类,它们可以共享一个打开的数据库连接。TestIt 项目中也提供了一个示例。
- 该程序是用 Visual Studio 6 编写的,但可以轻松移植到 Visual Studio .NET。它已在 MS-SQL 7 上测试过,但也将与更新版本的 SQL 兼容。
致谢
该代码基于 Carlos Antollini 开发的 AdoDatabase 的早期版本之一。
我根据自己的需要对其进行了更改和扩展。
历史
- 2009 年 8 月 2 日,2.01 版 - 首个发布版本。