Szybki start: Połącz się i zapytaj za pomocą C++ i ODBC

Dotyczy:programu SQL ServerAzure SQL Database

W tym szybkim starcie budujesz aplikację konsolową C++ na Windows, Linux lub macOS. Aplikacja łączy się z bazą AdventureWorksLT danych, używając sterownika Microsoft ODBC 18 dla SQL Server, wiąże parametr zapytania, wykonuje zapytanie i odczytuje wiersze wyników.

Prerequisites

  • Microsoft ODBC Driver 18 dla SQL Server. Zainstaluj sterownik dla Windows, Linux lub macOS.
  • Kompilator C++17 oraz pliki programistyczne ODBC na platformie:
    • Na Windows zainstaluj Visual Studio 2022 lub Build Tools for Visual Studio 2022 wraz z programowaniem desktopowym z C++. Pakiet SDK systemu Windows zawiera nagłówki ODBC oraz odbc32.lib.
    • Na Linuksie zainstaluj kompilator C++ oraz pakiet programistyczny unixODBC dla swojej dystrybucji. Pakiet dostarcza nagłówki ODBC oraz libodbc.
    • Na macOS zainstaluj narzędzia wiersza poleceń Xcode oraz unixODBC z Homebrew.
  • Baza danych w Azure SQL Database lub SQL Server, która zawiera przykładowe AdventureWorksLT dane. W przypadku Azure SQL Database wybierz przykładowe źródło danych podczastworzenia pojedynczej bazy danych. Dla SQL Server przywróć kopię zapasową AdventureWorksLT z przykładowych baz danych AdventureWorks.

Zweryfikowaj sterownika

Potwierdź, że menedżer sterowników znajduje sterownik Microsoft ODBC Driver 18 dla SQL Server.

Wykonaj to polecenie w PowerShell:

Get-OdbcDriver -Name "ODBC Driver 18 for SQL Server"

Każde polecenie powinno zawierać ODBC Driver 18 for SQL Server. Jeśli sterownik nie jest wymieniony, zainstaluj go ponownie przed kontynuacją.

Konfiguruj połączenie

Aplikacja odczytuje cały parametry połączenia ze zmiennej środowiskowejODBC_CONNECTION_STRING. Nie wyświetla parametry połączenia ani nie akceptuje go jako argumentu wiersza poleceń.

Użyj parametrów połączenia sterownika Driver 18, które są prawidłowe dla twojej bazy danych i metody uwierzytelniania. Poniższy przykład uwierzytelniania SQL działa na Windows, Linux i macOS, gdy baza danych umożliwia uwierzytelnianie SQL:

Driver={ODBC Driver 18 for SQL Server};Server=tcp:<server>,1433;Database=<database>;UID=<user_id>;PWD=<password>;Encrypt=yes;TrustServerCertificate=no;

Aby poznać opcje uwierzytelniania w usłudze Microsoft Entra, zobacz Używanie identyfikatora Microsoft Entra ID ze sterownikiem ODBC. Aby poznać wszystkie obsługiwane ustawienia, zobacz DSN i słowa kluczowe oraz atrybuty parametrów połączenia.

Sterownik Microsoft ODBC 18 domyślnie umożliwia szyfrowanie. Określ Encrypt=yes tak, aby wymagania aplikacji były jednoznaczne. Trzymaj TrustServerCertificate=no w produkcji, aby sterownik weryfikował certyfikat serwera. Certyfikat musi dopasować nazwę serwera i łańcuch do organu certyfikacyjnego, któremu klient ufa. Aby uzyskać wskazówki dotyczące konfiguracji, zobacz Błędy walidacji certyfikatu.

Caution

TrustServerCertificate=yes Pomija walidację certyfikatów. Używaj tego tylko w izolowanym lokalnym środowisku programistycznym podczas konfigurowania certyfikatu, któremu klient ufa. Nie używaj go w środowisku produkcyjnym.

Ustaw zmienną środowiskową bez dodawania parametrów połączenia do historii powłoki.

Open Developer PowerShell for VS 2022. Wykonaj te polecenia, a następnie wklej parametry połączenia w monicie:

$secureConnectionString = Read-Host "ODBC connection string" -AsSecureString
$env:ODBC_CONNECTION_STRING = [System.Net.NetworkCredential]::new(
    "", $secureConnectionString).Password
$secureConnectionString = $null

Zmienna środowiskowa utrzymuje parametry połączenia poza plikiem źródłowym, ale proces i jego procesy potomne mogą go odczytać. W aplikacjach produkcyjnych stosuj uwierzytelnianie Microsoft Entra, gdzie to możliwe, i pobieraj sekrety z bezpiecznego magazynu w czasie działania.

Tworzenie aplikacji

  1. Stwórz katalog projektów i zmień go na niego:

    New-Item -ItemType Directory odbc-quickstart
    Set-Location odbc-quickstart
    

  1. Utwórz plik o nazwie odbc-quickstart.cpp zawierający następujący kod:

    #ifdef _WIN32
    #include <windows.h>
    #endif
    
    #include <sql.h>
    #include <sqlext.h>
    #include <sqltypes.h>
    
    #include <cstdlib>
    #include <iomanip>
    #include <iostream>
    #include <string>
    
    std::string ReadEnvironmentVariable(const char* name)
    {
    #ifdef _WIN32
        char* value = nullptr;
        std::size_t length = 0;
        if (_dupenv_s(&value, &length, name) != 0 || value == nullptr)
            return {};
    
        std::string result(value);
        std::free(value);
        return result;
    #else
        const char* value = std::getenv(name);
        return value == nullptr ? std::string{} : value;
    #endif
    }
    
    void PrintDiagnostics(SQLSMALLINT handleType, SQLHANDLE handle)
    {
        SQLCHAR state[6];
        SQLINTEGER nativeError;
        SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
        SQLSMALLINT messageLength;
    
        for (SQLSMALLINT record = 1;
             SQL_SUCCEEDED(SQLGetDiagRec(handleType, handle, record, state,
                                         &nativeError, message, sizeof(message),
                                         &messageLength));
             ++record)
        {
            std::cerr << '[' << state << "] (" << nativeError << ") "
                      << message << '\n';
        }
    }
    
    bool Succeeded(SQLRETURN result, SQLSMALLINT handleType, SQLHANDLE handle)
    {
        if (SQL_SUCCEEDED(result))
            return true;
    
        PrintDiagnostics(handleType, handle);
        return false;
    }
    
    struct OdbcHandles
    {
        SQLHENV environment = SQL_NULL_HENV;
        SQLHDBC connection = SQL_NULL_HDBC;
        SQLHSTMT statement = SQL_NULL_HSTMT;
    
        ~OdbcHandles()
        {
            if (statement != SQL_NULL_HSTMT)
                SQLFreeHandle(SQL_HANDLE_STMT, statement);
            if (connection != SQL_NULL_HDBC)
            {
                SQLDisconnect(connection);
                SQLFreeHandle(SQL_HANDLE_DBC, connection);
            }
            if (environment != SQL_NULL_HENV)
                SQLFreeHandle(SQL_HANDLE_ENV, environment);
        }
    };
    
    int main()
    {
        std::string connectionString =
            ReadEnvironmentVariable("ODBC_CONNECTION_STRING");
        if (connectionString.empty())
        {
            std::cerr << "Set ODBC_CONNECTION_STRING before running.\n";
            return 1;
        }
    
        OdbcHandles handles;
        SQLRETURN result = SQLAllocHandle(
            SQL_HANDLE_ENV, SQL_NULL_HANDLE, &handles.environment);
        if (!SQL_SUCCEEDED(result))
        {
            std::cerr << "Unable to allocate an ODBC environment handle.\n";
            return 1;
        }
    
        result = SQLSetEnvAttr(
            handles.environment,
            SQL_ATTR_ODBC_VERSION,
            reinterpret_cast<SQLPOINTER>(SQL_OV_ODBC3_80),
            0);
        if (!Succeeded(result, SQL_HANDLE_ENV, handles.environment))
            return 1;
    
        result = SQLAllocHandle(
            SQL_HANDLE_DBC, handles.environment, &handles.connection);
        if (!Succeeded(result, SQL_HANDLE_ENV, handles.environment))
            return 1;
    
        result = SQLDriverConnect(
            handles.connection,
            nullptr,
            reinterpret_cast<SQLCHAR*>(connectionString.data()),
            SQL_NTS,
            nullptr,
            0,
            nullptr,
            SQL_DRIVER_NOPROMPT);
        if (!Succeeded(result, SQL_HANDLE_DBC, handles.connection))
            return 1;
    
        result = SQLAllocHandle(
            SQL_HANDLE_STMT, handles.connection, &handles.statement);
        if (!Succeeded(result, SQL_HANDLE_DBC, handles.connection))
            return 1;
    
        SQLINTEGER minimumProductId = 0;
        SQLLEN minimumProductIdLength = 0;
        result = SQLBindParameter(
            handles.statement,
            1,
            SQL_PARAM_INPUT,
            SQL_C_SLONG,
            SQL_INTEGER,
            10,
            0,
            &minimumProductId,
            0,
            &minimumProductIdLength);
        if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
            return 1;
    
        SQLCHAR query[] =
            "SELECT TOP (5) ProductID, Name "
            "FROM SalesLT.Product "
            "WHERE ProductID > ? "
            "ORDER BY ProductID;";
        result = SQLExecDirect(handles.statement, query, SQL_NTS);
        if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
            return 1;
    
        std::cout << "Product ID  Name\n"
                  << "----------  ----\n";
    
        while (SQL_SUCCEEDED(result = SQLFetch(handles.statement)))
        {
            SQLINTEGER productId;
            SQLLEN productIdLength;
            SQLCHAR productName[256];
            SQLLEN productNameLength;
    
            result = SQLGetData(
                handles.statement, 1, SQL_C_SLONG, &productId,
                sizeof(productId), &productIdLength);
            if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
                return 1;
    
            result = SQLGetData(
                handles.statement, 2, SQL_C_CHAR, productName,
                sizeof(productName), &productNameLength);
            if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
                return 1;
    
            std::cout << std::left << std::setw(12) << productId
                      << productName << '\n';
        }
    
        if (result != SQL_NO_DATA)
        {
            PrintDiagnostics(SQL_HANDLE_STMT, handles.statement);
            return 1;
        }
    
        return 0;
    }
    

Aplikacja korzysta wyłącznie ze standardowego API ODBC, więc zawiera nagłówki menedżera sterowników oraz linki do biblioteki menedżera sterowników. Parametry połączenia określają użycie sterownika Microsoft ODBC Driver 18 for SQL Server w czasie wykonywania.

SQL_DRIVER_NOPROMPT uniemożliwia SQLDriverConnect otwarcie dialogu konfiguracyjnego. Jeśli parametry połączenia są niepełne, wywołanie zwraca błąd, a aplikacja wyświetla każdy rekord diagnostyczny.

Zapytanie wiąże 0 jako parametr SQL Server typu int, odczytuje pierwszych pięć produktów w SalesLT.Product i pobiera identyfikator oraz nazwę każdego produktu za pomocą SQLGetData.

Kompilowanie i uruchamianie aplikacji

  1. W tym samym oknie PowerShell dla deweloperów kompiluj aplikację:

    cl /std:c++17 /EHsc /W4 odbc-quickstart.cpp /link odbc32.lib
    
  2. Uruchom aplikację:

    .\odbc-quickstart.exe
    

Standardowe wyjście aplikacji to:

Product ID  Name
----------  ----
680         HL Road Frame - Black, 58
706         HL Road Frame - Red, 58
707         Sport-100 Helmet, Red
708         Sport-100 Helmet, Black
709         Mountain Bike Socks, M

Wyczyść parametry połączenia po zakończeniu gry.

Remove-Item Env:\ODBC_CONNECTION_STRING