在C++中调用 MS SQL 存储过程

Calling an MS SQL Stored Procedure in C++

本文关键字:SQL 存储过程 MS 调用 C++      更新时间:2023-10-16

我正在编写一个连接到MS SQL (2012(服务器上的存储过程的应用程序。该过程用于将数据插入数据库。我在理解如何定义与存储过程的连接、将变量绑定到参数以及将这些参数与存储过程中的变量相关联时遇到了真正的麻烦。我花了几天时间浏览 MSDN 阅读 API 并尝试遵循此处的示例,但似乎有很多方法可以做到这一点,以至于我看不到"树木的木头"。我想我已经有了基本的结构,但我在细节上摔倒了。

以下是我到目前为止的代码。为简单起见,我省略了数据库连接代码。该函数是类"rigDatabase"的一部分,该类具有各种SQL句柄的私有成员。

我遇到的主要问题是对SQLSetDescField的调用。根据Microsoft提供的文档和示例,这些调用应该有效,但返回HY092 - "无效的属性/选项标识符"。这就是我需要帮助的。最近,我尝试记录 ODBC 驱动程序管理器的输出,以查看这是否对此事有所了解。对 SQLSetDescField 的一个调用的输出可以在存储过程定义下面看到。

注意:我还没有尝试将SQL嵌入C的更简单的方法。我正在尝试与现有基础架构(存储过程(接口。

SQLRETURN rigDatabase::send_SQL(const char* filename, 
const char* extn, 
const char* path, 
DWORD& fSize, 
const char* rigName, 
FILETIME& created,
const char* notes) {
SQLHDESC hIpd = NULL;
SQLINTEGER    PartIDInd = 0;
SQL_TIMESTAMP_STRUCT datetime2;
datetime2.year = fileDate.wYear;
datetime2.month = fileDate.wMonth;
datetime2.day = fileDate.wDay;
datetime2.hour = fileDate.wHour;
datetime2.minute = fileDate.wMinute;
datetime2.second = fileDate.wSecond;
datetime2.fraction = fileDate.wMilliseconds;
retcode = SQLPrepareA(sqlStmtHandle, (SQLCHAR*)"{call insertTestRigDataTest(?, ?, ?, ?, ?, ?, ?)}", SQL_NTS);
retcode = SQLBindParameter(sqlStmtHandle, 1, SQL_PARAM_INPUT, SQL_C_CHAR,           SQL_CHAR,           FILENAME_MAX,   0, (SQLPOINTER)filename,    0, NULL);
retcode = SQLBindParameter(sqlStmtHandle, 2, SQL_PARAM_INPUT, SQL_C_CHAR,           SQL_CHAR,           MAX_PATH,       0, (SQLPOINTER)path,        0, NULL);
retcode = SQLBindParameter(sqlStmtHandle, 3, SQL_PARAM_INPUT, SQL_C_CHAR,           SQL_CHAR,           BUF_SIZE,       0, (SQLPOINTER)rigName,     0, NULL);
retcode = SQLBindParameter(sqlStmtHandle, 4, SQL_PARAM_INPUT, SQL_C_ULONG,          SQL_INTEGER,        0,              0, (SQLPOINTER)fSize,       0, &PartIDInd);
retcode = SQLBindParameter(sqlStmtHandle, 5, SQL_PARAM_INPUT, SQL_C_CHAR,           SQL_CHAR,           4,              0, (SQLPOINTER)extn,        0, NULL);
retcode = SQLBindParameter(sqlStmtHandle, 6, SQL_PARAM_INPUT, SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP, sizeof(SQL_TIMESTAMP_STRUCT), 0, &datetime2, 0, NULL);
retcode = SQLBindParameter(sqlStmtHandle, 7, SQL_PARAM_INPUT, SQL_C_CHAR,           SQL_CHAR,           4000,       0, (SQLPOINTER)notes,       0, NULL);
retcode = SQLGetStmtAttrA(sqlStmtHandle, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0);
// All calls to SQLSetDescField below return -1
//  SQLGetDiagRecA returns "Invalid attribute/option identifier"
retcode = SQLSetDescField(hIpd, 1, SQL_DESC_NAME, "@Filename", SQL_NTS);
retcode = SQLSetDescField(hIpd, 2, SQL_DESC_NAME, "@Path", SQL_NTS);
retcode = SQLSetDescField(hIpd, 3, SQL_DESC_NAME, "@Rigname", SQL_NTS);
retcode = SQLSetDescField(hIpd, 4, SQL_DESC_NAME, "@Size", SQL_NTS);
retcode = SQLSetDescField(hIpd, 5, SQL_DESC_NAME, "@Extn", SQL_NTS);
retcode = SQLSetDescField(hIpd, 6, SQL_DESC_NAME, "@Created", SQL_NTS);
retcode = SQLSetDescField(hIpd, 7, SQL_DESC_NAME, "@Notes", SQL_NTS);
retcode = SQLExecute(sqlStmtHandle);    
return EXIT_FAILURE;
}
// Function to convert from FILETIME to int64
unsigned __int64 FILETIME_to_int64( const FILETIME& ac_FileTime ) {
ULARGE_INTEGER    lv_Large;
lv_Large.LowPart = ac_FileTime.dwLowDateTime;
lv_Large.HighPart = ac_FileTime.dwHighDateTime;
return lv_Large.QuadPart;
}

MS SQL 存储过程

USE [TestRigDataTest]
GO
/****** Object:  StoredProcedure [dbo].[insertTestRigDataTest]    Script Date: 16/10/2018 10:14:34 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[insertTestRigDataTest] 
(
@fileName   nvarchar(255),
@path       nvarchar(255),
@rigName    nvarchar(255),
@size       [numeric](18, 0),
@extn       nvarchar(255),
@created    [datetime],
@notes      nvarchar(4000) = NULL
)
AS
BEGIN
SET NOCOUNT ON;
declare @id int
declare @tmpNotes nvarchar(4000)
set @tmpNotes=''
select @id=id,@tmpNotes=notes from [TestRigDataTest].[dbo].[RigData]
where
[Filename]=@filename and
[Path]=@Path and
[Size]=@Size and
[Extension]=@Extn and
[created]=@created
if @@rowcount=0
begin
INSERT INTO [TestRigDataTest].[dbo].[RigData] (
[Filename], [Path], [Rigname] ,[UploadDate] ,[Size] ,[Extension]  ,[created]  ,[notes]
)
VALUES (
@fileName, @path, @rigName, getdate(), @size, @extn, @created, @notes
)
end
else
begin
if @notes != ''
begin
update [TestRigDataTest].[dbo].[RigData] set [notes]=@tmpNotes  + CHAR(13)+CHAR(10) + @notes
where id=@id
end
end
END

ODBC Driver Manager Partial Trace Log:NB:SQLSetDescField is #defined to SQLSetDescFieldW

AirCatFeeder    26f0-1e50   ENTER SQLSetDescFieldW 
SQLHDESC            0x00000000004CB9E8
SQLSMALLINT                  2 
SQLSMALLINT               1011 <SQL_DESC_NAME>
SQLPOINTER          0x000000013FBA15EC [      -3] "??h 0"
SQLINTEGER                  -3 
AirCatFeeder    26f0-1e50   ENTER SQLSetDescField 
SQLHDESC            0x00000000004CB9E8
SQLSMALLINT                  2 
SQLSMALLINT               1011 <SQL_DESC_NAME>
SQLPOINTER          0x000000013FBA15EC [      -3] "@Path 0"
SQLINTEGER                  -3 
AirCatFeeder    26f0-1e50   EXIT  SQLSetDescField  with return code -1 (SQL_ERROR)
SQLHDESC            0x00000000004CB9E8
SQLSMALLINT                  2 
SQLSMALLINT               1011 <SQL_DESC_NAME>
SQLPOINTER          0x000000013FBA15EC [      -3] "@Path 0"
SQLINTEGER                  -3 
DIAG [HY092] [Microsoft][ODBC SQL Server Driver]Invalid attribute/option identifier (0) 
AirCatFeeder    26f0-1e50   EXIT  SQLSetDescFieldW  with return code -1 (SQL_ERROR)
SQLHDESC            0x00000000004CB9E8
SQLSMALLINT                  2 
SQLSMALLINT               1011 <SQL_DESC_NAME>
SQLPOINTER          0x000000013FBA15EC [      -3] "??h 0"
SQLINTEGER                  -3 
DIAG [HY092] [Microsoft][ODBC SQL Server Driver]Invalid attribute/option identifier (0) 

问题是使用宽字符串。SQLSetDescField#definesSQLSetDescFieldW,但字符串文字没有被标记为宽字符串。在字符串定义之前,我缺少"L"。下面是正确代码的示例。

retcode = SQLSetDescField(hIpd, 1, SQL_DESC_NAME, L"@filename", SQL_NTS);

有趣的是,我尝试使用MSDN的演示代码,该代码基于宽字符串,但是他们自己的代码出现了此错误。当你试图学习时,这非常令人沮丧,但也许它们就像旧的麦卡诺套件一样 - 故意犯错误。我当然不会忘记这一课!