Appel de procédures stockées compilées en mode natif à partir d’applications d’accès aux données
S’applique à : SQL Server Azure SQL Database Azure SQL Managed Instance
Cette rubrique fournit des instructions pour appeler des procédures stockées compilées en mode natif à partir d'applications d'accès aux données.
Points de conseils
Les curseurs ne peuvent pas effectuer une itération au sein d'une procédure stockée compilée en mode natif.
L'appel de procédures stockées compilées en mode natif n'est pas pris en charge à partir des modules CLR avec connexion contextuelle.
SqlClient
Pour SqlClient, il n'y a aucune distinction entre l'exécution préparée et directe. Exécutez les procédures stockées avec SqlCommand à l’aide de la syntaxe
CommandType = CommandType.StoredProcedure
.SqlClient ne prend pas en charge les appels de procédure RPC préparés.
SqlClient ne prend pas en charge la récupération des informations de schéma uniquement (découverte des métadonnées) sur les jeux de résultats retournés par une procédure stockée compilée en mode natif (
CommandType.SchemaOnly
).- Utilisez plutôt sp_describe_first_result_set (Transact-SQL).
Microsoft ODBC Driver for SQL Server. (MSODBCSQL)
- Les versions de SQL Server Native Client antérieures à SQL Server 2012 (11.x) ne prennent pas en charge la récupération des informations de schéma uniquement (découverte des métadonnées) sur les jeux de résultats retournés par une procédure stockée compilée en mode natif.
- Utilisez plutôt sp_describe_first_result_set (Transact-SQL).
- Cet exemple a été écrit à l'origine pour le client natif SQL Server (sqlncli.h) mais a été mis à jour pour utiliser le pilote Microsoft ODBC pour SQL Server (MSODBCSQL). SQL Server Native Client (SNAC) n'est pas livré avec :- SQL Server 2022 (16.x) et versions ultérieures - SQL Server Management Studio 19 et versions ultérieures. Le SQL Server Native Client (SQLNCLI ou SQLNCLI11) et l’ancien fournisseur OLE DB Microsoft pour SQL Server (SQLOLEDB) ne sont pas recommandés pour le développement de nouvelles applications. Pour les nouveaux projets, utilisez l'un des gestionnaires suivants :- Pilote ODBC Microsoft pour SQL Server - Microsoft OLE DB pour SQL ServerPour SQLNCLI qui est livré en tant que composant de SQL Server Database Engine (versions 2012 à 2019), voir cette exception relative au cycle de vie du support.
ODBC
Les recommandations suivantes s'appliquent aux appels de procédures stockées compilées en mode natif à l'aide du pilote ODBC dans SQL Server Native Client.
Appel pré-payé: La manière la plus efficace d'appeler une procédure stockée une seule fois est d'émettre un appel RPC direct à l'aide des clauses SQLExecDirectet ODBC CALL. N'utilisez pas l'instruction Transact-SQL EXECUTE. Si une procédure stockée doit être appelée plusieurs fois, l'exécution préparée est plus efficace.
Appeler plusieurs fois : La manière la plus efficace d'appeler une procédure stockée du serveur SQL plus d'une fois est d'utiliser des appels de procédure RPC préparés. Les appels RPC préparés sont mis en œuvre comme suit à l'aide du pilote ODBC dans SQL Server Native Client :
- Ouvrez une connexion à la base de données.
- Liez les paramètres à l’aide de SQLBindParameter.
- Préparez l’appel de procédure à l’aide de SQLPrepare.
- Exécutez la procédure stockée plusieurs fois à l’aide de SQLExecute.
Code C pour ODBC
Le fragment de code C suivant illustre l'exécution préparée d'une procédure stockée conçue pour ajouter des lignes d'article à une commande. SQLPrepare n’est appelé qu’une seule fois. Et SQLExecute est appelé plusieurs fois, à chaque exécution de la procédure.
// Bind parameters
// 1 - OrdNo
SQLRETURN returnCode = SQLBindParameter(
hstmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 10, 0,
&order.OrdNo, sizeof(SQLINTEGER), NULL);
if (returnCode != SQL_SUCCESS && returnCode != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
// 2, 3, 4 - ItemNo, ProdCode, Qty
...
// Prepare stored procedure
returnCode = SQLPrepare(hstmt, (SQLTCHAR *) _T("{call ItemInsert(?, ?, ?, ?)}"),
SQL_NTS);
for (unsigned int i = 0; i < order.ItemCount; i++) {
ItemNo = order.ItemNo[i];
ProdCode = order.ProdCode[i];
Qty = order.Qty[i];
// Execute stored procedure
returnCode = SQLExecute(hstmt);
if (returnCode != SQL_SUCCESS && returnCode != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
}
Utilisation du pilote ODBC pour exécuter une procédure stockée compilée en mode natif
Cet exemple montre comment lier des paramètres et exécuter des procédures stockées à l'aide du pilote ODBC SQL Server Native Client. Il est compilé en une application console qui insère une seule commande à l'aide d'une exécution directe et insère les détails de la commande à l'aide d'une exécution préparée.
Pour exécuter cet exemple :
Créez une base de données d'exemple avec un groupe de fichiers de données mémoire optimisé. Pour plus d’informations sur la création d’une base de données avec un groupe de fichiers de données optimisées en mémoire, consultez Création d’une table optimisée en mémoire et d’une procédure stockée compilée en mode natif.
Créez une source de données ODBC appelée PrepExecSample qui pointe vers la base de données. Utiliser le pilote SQL Server Native Client. Vous pouvez également modifier l’exemple et utiliser le pilote Microsoft ODBC pour SQL Server.
Exécutez le script Transact-SQL (ci-dessous) sur l'exemple de base de données.
Compilez et exécutez l'exemple.
Vérifiez l'exécution du programme en exécutant une requête sur le contenu des tables :
SELECT * FROM dbo.Ord;
SELECT * FROM dbo.Item;
Transact-SQL préliminaire
Voici l'intégralité du code Transact-SQL qui crée les objets de base de données mémoire optimisés.
IF EXISTS (SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID=OBJECT_ID('dbo.OrderInsert'))
DROP PROCEDURE dbo.OrderInsert;
GO
IF EXISTS (SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID=OBJECT_ID('dbo.ItemInsert'))
DROP PROCEDURE dbo.ItemInsert;
GO
IF EXISTS (SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID=OBJECT_ID('dbo.Ord'))
DROP TABLE dbo.Ord;
GO
IF EXISTS (SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID=OBJECT_ID('dbo.Item'))
DROP TABLE dbo.Item;
GO
CREATE TABLE dbo.Ord
(
OrdNo INTEGER NOT NULL PRIMARY KEY NONCLUSTERED,
OrdDate DATETIME NOT NULL,
CustCode VARCHAR(5) NOT NULL)
WITH (MEMORY_OPTIMIZED=ON);
GO
CREATE TABLE dbo.Item
(
OrdNo INTEGER NOT NULL,
ItemNo INTEGER NOT NULL,
ProdCode INTEGER NOT NULL,
Qty INTEGER NOT NULL,
CONSTRAINT PK_Item PRIMARY KEY NONCLUSTERED (OrdNo,ItemNo))
WITH (MEMORY_OPTIMIZED=ON);
GO
CREATE PROCEDURE dbo.OrderInsert(
@OrdNo INTEGER, @CustCode VARCHAR(5))
WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
AS BEGIN ATOMIC WITH
(TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = 'english')
DECLARE @OrdDate datetime = GETDATE();
INSERT INTO dbo.Ord (OrdNo, CustCode, OrdDate)
VALUES (@OrdNo, @CustCode, @OrdDate);
END;
GO
CREATE PROCEDURE dbo.ItemInsert(
@OrdNo INTEGER, @ItemNo INTEGER, @ProdCode INTEGER, @Qty INTEGER)
WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
AS BEGIN ATOMIC WITH
(TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english')
INSERT INTO dbo.Item (OrdNo, ItemNo, ProdCode, Qty)
VALUES (@OrdNo, @ItemNo, @ProdCode, @Qty)
END
GO
Code C
Voici l'intégralité du code C :
// compile with: user32.lib odbc32.lib
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers.
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <windows.h>
#include "sql.h"
#include "sqlext.h"
#include "msodbcsql.h"
// cardinality of order item related array variables
#define ITEM_ARRAY_SIZE 20
// struct to pass order entry data
typedef struct OrdEntry_struct {
SQLINTEGER OrdNo;
SQLTCHAR CustCode[6];
SQLUINTEGER ItemCount;
SQLINTEGER ItemNo[ITEM_ARRAY_SIZE];
SQLINTEGER ProdCode[ITEM_ARRAY_SIZE];
SQLINTEGER Qty[ITEM_ARRAY_SIZE];
} OrdEntryData;
SQLHANDLE henv, hdbc, hstmt;
void ODBCError(
SQLHANDLE henv, SQLHANDLE hdbc,
SQLHANDLE hstmt, SQLHANDLE hdesc,
bool ShowError)
{
SQLRETURN r = 0;
SQLTCHAR szSqlState[6] = {0};
SQLINTEGER fNativeError = 0;
SQLTCHAR szErrorMsg[256] = {0};
SQLSMALLINT cbErrorMsgMax = sizeof(szErrorMsg) - 1;
SQLSMALLINT cbErrorMsg = 0;
TCHAR text[1024] = {0}, title[256] = {0};
if (hdesc != NULL)
r = SQLGetDiagRec(SQL_HANDLE_DESC, hdesc, 1, szSqlState,
&fNativeError, szErrorMsg, cbErrorMsgMax, &cbErrorMsg);
else {
if (hstmt != NULL)
r = SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, 1, szSqlState,
&fNativeError, szErrorMsg, cbErrorMsgMax, &cbErrorMsg);
else {
if (hdbc != NULL)
r = SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, szSqlState,
&fNativeError, szErrorMsg, cbErrorMsgMax, &cbErrorMsg);
else
r = SQLGetDiagRec(SQL_HANDLE_ENV, henv, 1, szSqlState,
&fNativeError, szErrorMsg, cbErrorMsgMax, &cbErrorMsg);
}
}
if (ShowError) {
_sntprintf_s(title, _countof(title), _TRUNCATE, _T("ODBC Error %i"),
fNativeError);
_sntprintf_s(text, _countof(text), _TRUNCATE, _T("[%s] - %s"),
szSqlState, szErrorMsg);
MessageBox(NULL, (LPCTSTR) text, (LPCTSTR) _T("ODBC Error"), MB_OK);
}
}
void connect() {
SQLRETURN r;
r = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv);
// This is an ODBC v3 application
r = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, 0);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, NULL, NULL, NULL, true);
exit(-1);
}
r = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
// Run in ANSI/implicit transaction mode
r = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT,
(SQLPOINTER) SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, NULL, NULL, NULL, true);
exit(-1);
}
TCHAR szConnStrIn[256] = _T("DSN=PrepExecSample");
r = SQLDriverConnect(hdbc, NULL, (SQLTCHAR *) szConnStrIn, SQL_NTS,
NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, NULL, NULL, true);
exit(-1);
}
}
void setup_ODBC_basics() {
SQLRETURN r;
r = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
}
void OrdEntry(OrdEntryData& order) {
// Simple order entry
SQLRETURN r;
SQLINTEGER ItemNo, ProdCode, Qty;
// Bind parameters for the Order
// 1 - OrdNo input
r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,
0, 0, &order.OrdNo, sizeof(SQLINTEGER), NULL);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
// 2 - Custcode input
r = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT,SQL_C_TCHAR, SQL_VARCHAR, 5, 0,
&order.CustCode, sizeof(order.CustCode), NULL);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
// Insert the order
r = SQLExecDirect(hstmt, (SQLTCHAR *) _T("{call OrderInsert(?, ?)}"),SQL_NTS);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
// Flush results & reset hstmt
r = SQLMoreResults(hstmt);
if (r != SQL_NO_DATA) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
r = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
// Bind parameters for the Items
// 1 - OrdNo
r = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 10, 0,
&order.OrdNo, sizeof(SQLINTEGER), NULL);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
// 2 - ItemNo
r = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 10, 0,
&ItemNo, sizeof(SQLINTEGER), NULL);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
// 3 - ProdCode
r = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 10, 0,
&ProdCode, sizeof(SQLINTEGER), NULL);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
// 4 - Qty
r = SQLBindParameter(hstmt, 4, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 10, 0,
&Qty, sizeof(SQLINTEGER), NULL);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
// Prepare to insert items one at a time
r = SQLPrepare(hstmt, (SQLTCHAR *) _T("{call ItemInsert(?, ?, ?, ?)}"),SQL_NTS);
for (unsigned int i = 0; i < order.ItemCount; i++) {
ItemNo = order.ItemNo[i];
ProdCode = order.ProdCode[i];
Qty = order.Qty[i];
r = SQLExecute(hstmt);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
}
// Flush results & reset hstmt
r = SQLMoreResults(hstmt);
if (r != SQL_NO_DATA) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
r = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
// Commit the transaction
r = SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);
if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
ODBCError(henv, hdbc, hstmt, NULL, true);
exit(-1);
}
}
void testOrderEntry() {
OrdEntryData order;
order.OrdNo = 1;
_tcscpy_s((TCHAR *) order.CustCode, _countof(order.CustCode), _T("CUST1"));
order.ItemNo[0] = 1;
order.ProdCode[0] = 10;
order.Qty[0] = 1;
order.ItemNo[1] = 2;
order.ProdCode[1] = 20;
order.Qty[1] = 2;
order.ItemNo[2] = 3;
order.ProdCode[2] = 30;
order.Qty[2] = 3;
order.ItemNo[3] = 4;
order.ProdCode[3] = 40;
order.Qty[3] = 4;
order.ItemCount = 4;
OrdEntry(order);
}
int _tmain() {
connect();
setup_ODBC_basics();
testOrderEntry();
}