快速入门:使用 C++ 和 ODBC 进行连接和查询

适用于:SQL ServerAzure SQL 数据库

在这个快速入门中,你需要在Windows、Linux或macOS上构建一个C++控制台应用程序。 该应用程序通过使用 Microsoft ODBC Driver 18 for SQL Server 连接到AdventureWorksLT数据库,绑定查询参数,执行查询并读取结果行。

先决条件

  • Microsoft ODBC Driver 18 for SQL Server. 安装Windows、Linux或macOS的驱动。
  • 一个 C++17 编译器以及该平台的 ODBC 开发文件:
    • 在 Windows 上,安装 Visual Studio 2022 或 Visual Studio 2022 构建工具,配合 C++ 桌面开发工作负载。 Windows SDK 提供 ODBC 头文件和 odbc32.lib。
    • 在Linux上,安装一个C++编译器和UnixODBC开发包作为你的发行版。 该软件包提供 ODBC 头文件和 libodbc。
    • 在macOS上,安装Xcode命令行工具和Homebrew的unixODBC。
  • 在Azure SQL 数据库或SQL Server中包含AdventureWorksLT样本数据的数据库。 对于 Azure SQL 数据库,创建单个数据库时选择示例数据源。 对于 SQL Server,请从 AdventureWorks 样本数据库中恢复AdventureWorksLT备份。

核实司机

确认驱动程序管理器能找到Microsoft ODBC Driver 18 for SQL Server。

在PowerShell中执行以下命令:

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

每个命令都应该包含 ODBC Driver 18 for SQL Server。 如果驱动没有列出,继续前先重新安装。

配置连接

应用程序从 ODBC_CONNECTION_STRING 环境变量中读取完整的连接字符串。 它不会显示 连接字符串,也不会将其作为命令行参数。

使用适用于你的数据库和身份验证方法的 Driver 18 连接字符串。 以下SQL认证示例在Windows、Linux和macOS上运行,前提是数据库允许SQL认证:

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

关于 Microsoft Entra 认证选项,请参见使用 Microsoft Entra ID 搭配 ODBC 驱动程序。 有关所有受支持的设置,请参见 DSN 和连接字符串关键字及属性。

Microsoft ODBC 驱动 18 默认启用加密功能。 指定 Encrypt=yes 以明确应用的需求。 在生产环境中保留 TrustServerCertificate=no,以便驱动程序验证服务器证书。 证书必须将服务器名称和链条与客户端信任的认证机构匹配。 有关配置指导,请参见证书验证失败。

Caution

TrustServerCertificate=yes 跳过证书验证。 在配置客户端信任的证书时,只用它进行局部独立开发。 请勿在生产环境中使用它。

设置环境变量时,不要将连接字符串添加到 Shell 历史记录中。

打开适用于 VS 2022 的开发人员 PowerShell。 运行以下命令,然后在提示时粘贴连接字符串:

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

环境变量将 连接字符串 排除在源文件之外,但进程及其子进程可以读取它。 对于生产应用,尽可能使用 Microsoft Entra 认证,并在运行时从安全存储中获取秘密。

创建应用程序

  1. 创建一个项目目录并切换到该目录:

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

  1. 使用以下代码创建名为 odbc-quickstart.cpp 的文件:

    #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;
    }
    

该应用程序仅使用标准的 ODBC API,因此包含驱动程序-管理器头部和指向驱动程序管理器库的链接。 连接字符串在运行时选择 Microsoft ODBC Driver 18 for SQL Server。

SQL_DRIVER_NOPROMPT 阻止 SQLDriverConnect 打开配置对话框。 如果 连接字符串 不完整,调用返回错误,应用程序会打印所有诊断记录。

该查询将 0 绑定为 SQL Server int 参数,读取 SalesLT.Product 中的前五个产品,并使用 SQLGetData 检索每个产品的 ID 和名称。

生成并运行应用程序

  1. 在同一个开发者PowerShell窗口中,编译应用程序:

    cl /std:c++17 /EHsc /W4 odbc-quickstart.cpp /link odbc32.lib
    
  2. 运行应用程序:

    .\odbc-quickstart.exe
    

该应用的标准输出为:

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

完成后,清除连接字符串。

Remove-Item Env:\ODBC_CONNECTION_STRING