快速入门:使用 Microsoft OLE DB 驱动程序进行连接和查询

在这个快速入门中,你需要用Visual Studio 2022及更高版本构建一个Windows C++控制台应用程序。 该应用程序使用 Microsoft OLE DB Driver 19 for SQL Server 连接到 Azure SQL 数据库、Microsoft Fabric 中的 SQL 数据库或 SQL Server。 它对样本数据执行参数化查询 AdventureWorksLT 并验证结果。

Prerequisites

创建 SQL 数据库

在以下平台之一创建或连接SQL数据库:

在本次快速启动中,选择或加载 AdventureWorksLT 样本数据。

对于 SQL Server 容器,创建一个容器并用一个命令加载示例数据:

sqlcmd create mssql --accept-eula --using https://aka.ms/AdventureWorksLT.bak

对于已有的 SQL Server 实例,可以从 AdventureWorks 示例数据库中恢复AdventureWorksLT备份。

本快速入门中的 OLE DB 客户端应用程序运行于 Windows 上。 SQL Server 容器可以在其他支持的主机上运行。

核实司机

为你的 Visual Studio 版本打开一个 x64 原生工具命令提示符,然后执行以下命令:

Important

该快速启动中的命令使用命令提示符语法。 在x64 Native Tools命令提示符中运行,提示符以 >. 结尾。 不要在 PowerShell 中运行它们,因为那里的提示符以 PS 开头。

reg query HKCR\MSOLEDBSQL19

该命令显示注册 MSOLEDBSQL19 的提供者。

配置连接

在x64 Native Tools命令提示符中设置 OLEDB_CONNECTION_STRING 。 应用程序从环境中读取 连接字符串,但不会显示它。

对于Azure SQL 数据库或Fabric中的SQL数据库,可以使用Microsoft Entra交互式认证。 用你的 SQL 资源中的服务器、数据库和 Microsoft Entra 用户 ID 替换占位符:

set "OLEDB_CONNECTION_STRING=Provider=MSOLEDBSQL19;Data Source=tcp:<server>,1433;Initial Catalog=<database>;Authentication=ActiveDirectoryInteractive;User ID=<user_id>;Use Encryption for Data=Mandatory;Trust Server Certificate=false;"

对于 Fabric 中的 SQL 数据库,你的身份需要对该数据库项具有读取权限。 不支持SQL认证。 有关详细信息,请参阅 Microsoft Fabric SQL 数据库中的身份验证。

对于接受Windows认证的现有SQL Server实例,请使用Integrated Security=SSPI:

set "OLEDB_CONNECTION_STRING=Provider=MSOLEDBSQL19;Data Source=tcp:<server>,1433;Initial Catalog=<database>;Integrated Security=SSPI;Use Encryption for Data=Mandatory;Trust Server Certificate=false;"

对于接受SQL认证的SQL Server实例或容器,请使用Authentication=SqlPassword:

set "OLEDB_USER_ID=<user_id>"
set "OLEDB_PASSWORD=<password>"
set "OLEDB_CONNECTION_STRING=Provider=MSOLEDBSQL19;Data Source=tcp:<server>,1433;Initial Catalog=<database>;Authentication=SqlPassword;User ID=%OLEDB_USER_ID%;Password=%OLEDB_PASSWORD%;Use Encryption for Data=Mandatory;Trust Server Certificate=false;"

SQL Server 证书必须与服务器名称匹配,并且其证书链必须链接到 Windows 客户端信任的证书颁发机构(CA)。 对于 SQL Server 容器,在容器中配置传输层安全(TLS),并在运行应用前在 Windows 客户端注册发布的 CA。 欲了解更多信息,请参见“加密Linux上的Linux 上的 SQL Server连接”和“配置SQL Server 数据库引擎以加密连接”。

创建应用程序

  1. 创建一个项目目录:

    mkdir oledb-quickstart
    cd oledb-quickstart
    
  2. 使用以下代码创建名为 oledb-quickstart.cpp 的文件:

    #include <windows.h>
    #include <oledb.h>
    #include <msdasc.h>
    #include <msoledbsql.h>
    
    #include <cstddef>
    #include <iomanip>
    #include <iostream>
    #include <string>
    
    template <typename T>
    void Release(T*& pointer)
    {
        if (pointer != nullptr)
        {
            pointer->Release();
            pointer = nullptr;
        }
    }
    
    struct ParameterData
    {
        DBSTATUS status;
        DBLENGTH length;
        LONG value;
    };
    
    constexpr std::size_t productNameCharacters = 51;
    
    struct RowData
    {
        DBSTATUS productIdStatus;
        DBLENGTH productIdLength;
        LONG productId;
        DBSTATUS nameStatus;
        DBLENGTH nameLength;
        wchar_t name[productNameCharacters];
    };
    
    std::wstring ReadEnvironmentVariable(const wchar_t* name)
    {
        const DWORD length = GetEnvironmentVariableW(name, nullptr, 0);
        if (length == 0)
            return {};
    
        std::wstring value(length, L'\0');
        const DWORD copied = GetEnvironmentVariableW(
            name,
            value.data(),
            length);
        if (copied == 0 || copied >= length)
            return {};
    
        value.resize(copied);
        return value;
    }
    
    int wmain()
    {
        const std::wstring connectionString =
            ReadEnvironmentVariable(L"OLEDB_CONNECTION_STRING");
        if (connectionString.empty())
        {
            std::wcerr << L"Set OLEDB_CONNECTION_STRING before running.\n";
            return 1;
        }
    
        HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
        if (FAILED(result))
        {
            std::wcerr << L"COM initialization failed: 0x"
                       << std::hex << result << L'\n';
            return 1;
        }
    
        IDataInitialize* dataInitialize = nullptr;
        IDBInitialize* dbInitialize = nullptr;
        IDBCreateSession* createSession = nullptr;
        IDBCreateCommand* createCommand = nullptr;
        ICommandText* commandText = nullptr;
        ICommandWithParameters* commandParameters = nullptr;
        IAccessor* parameterAccessor = nullptr;
        IRowset* rowset = nullptr;
        IAccessor* rowAccessor = nullptr;
        HACCESSOR parameterHandle = DB_NULL_HACCESSOR;
        HACCESSOR rowHandle = DB_NULL_HACCESSOR;
        HROW* rows = nullptr;
        DBCOUNTITEM rowCount = 0;
        bool initialized = false;
    
        do
        {
            result = CoCreateInstance(
                CLSID_MSDAINITIALIZE,
                nullptr,
                CLSCTX_INPROC_SERVER,
                IID_IDataInitialize,
                reinterpret_cast<void**>(&dataInitialize));
            if (FAILED(result))
                break;
    
            result = dataInitialize->GetDataSource(
                nullptr,
                CLSCTX_INPROC_SERVER,
                connectionString.c_str(),
                IID_IDBInitialize,
                reinterpret_cast<IUnknown**>(&dbInitialize));
            if (FAILED(result))
                break;
    
            result = dbInitialize->Initialize();
            if (FAILED(result))
                break;
            initialized = true;
    
            result = dbInitialize->QueryInterface(
                IID_IDBCreateSession,
                reinterpret_cast<void**>(&createSession));
            if (FAILED(result))
                break;
    
            result = createSession->CreateSession(
                nullptr,
                IID_IDBCreateCommand,
                reinterpret_cast<IUnknown**>(&createCommand));
            if (FAILED(result))
                break;
    
            result = createCommand->CreateCommand(
                nullptr,
                IID_ICommandText,
                reinterpret_cast<IUnknown**>(&commandText));
            if (FAILED(result))
                break;
    
            result = commandText->SetCommandText(
                DBGUID_DBSQL,
                const_cast<wchar_t*>(
                    L"SELECT TOP (5) ProductID, Name "
                    L"FROM SalesLT.Product "
                    L"WHERE ProductID > ? "
                    L"ORDER BY ProductID;"));
            if (FAILED(result))
                break;
    
            result = commandText->QueryInterface(
                IID_ICommandWithParameters,
                reinterpret_cast<void**>(&commandParameters));
            if (FAILED(result))
                break;
    
            DB_UPARAMS parameterOrdinal = 1;
            wchar_t parameterType[] = L"int";
            DBPARAMBINDINFO parameterInfo = {};
            parameterInfo.pwszDataSourceType = parameterType;
            parameterInfo.ulParamSize = sizeof(LONG);
            parameterInfo.dwFlags = DBPARAMFLAGS_ISINPUT;
            parameterInfo.bPrecision = 10;
    
            result = commandParameters->SetParameterInfo(
                1,
                &parameterOrdinal,
                &parameterInfo);
            if (FAILED(result))
                break;
    
            result = commandText->QueryInterface(
                IID_IAccessor,
                reinterpret_cast<void**>(&parameterAccessor));
            if (FAILED(result))
                break;
    
            DBBINDING parameterBinding = {};
            parameterBinding.iOrdinal = 1;
            parameterBinding.obStatus = offsetof(ParameterData, status);
            parameterBinding.obLength = offsetof(ParameterData, length);
            parameterBinding.obValue = offsetof(ParameterData, value);
            parameterBinding.dwPart = DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE;
            parameterBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED;
            parameterBinding.eParamIO = DBPARAMIO_INPUT;
            parameterBinding.cbMaxLen = sizeof(LONG);
            parameterBinding.wType = DBTYPE_I4;
            parameterBinding.bPrecision = 10;
    
            DBBINDSTATUS parameterBindStatus = DBBINDSTATUS_OK;
            result = parameterAccessor->CreateAccessor(
                DBACCESSOR_PARAMETERDATA,
                1,
                &parameterBinding,
                sizeof(ParameterData),
                &parameterHandle,
                &parameterBindStatus);
            if (FAILED(result) || parameterBindStatus != DBBINDSTATUS_OK)
            {
                if (SUCCEEDED(result))
                    result = E_FAIL;
                break;
            }
    
            ParameterData parameter = {
                DBSTATUS_S_OK,
                sizeof(LONG),
                0
            };
            DBPARAMS parameters = {
                &parameter,
                1,
                parameterHandle
            };
    
            result = commandText->Execute(
                nullptr,
                IID_IRowset,
                &parameters,
                nullptr,
                reinterpret_cast<IUnknown**>(&rowset));
            if (FAILED(result))
                break;
    
            result = rowset->QueryInterface(
                IID_IAccessor,
                reinterpret_cast<void**>(&rowAccessor));
            if (FAILED(result))
                break;
    
            DBBINDING rowBindings[2] = {};
            rowBindings[0].iOrdinal = 1;
            rowBindings[0].obStatus = offsetof(RowData, productIdStatus);
            rowBindings[0].obLength = offsetof(RowData, productIdLength);
            rowBindings[0].obValue = offsetof(RowData, productId);
            rowBindings[0].dwPart =
                DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE;
            rowBindings[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
            rowBindings[0].eParamIO = DBPARAMIO_NOTPARAM;
            rowBindings[0].cbMaxLen = sizeof(LONG);
            rowBindings[0].wType = DBTYPE_I4;
            rowBindings[0].bPrecision = 10;
    
            rowBindings[1].iOrdinal = 2;
            rowBindings[1].obStatus = offsetof(RowData, nameStatus);
            rowBindings[1].obLength = offsetof(RowData, nameLength);
            rowBindings[1].obValue = offsetof(RowData, name);
            rowBindings[1].dwPart =
                DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE;
            rowBindings[1].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
            rowBindings[1].eParamIO = DBPARAMIO_NOTPARAM;
            rowBindings[1].cbMaxLen =
                productNameCharacters * sizeof(wchar_t);
            rowBindings[1].wType = DBTYPE_WSTR;
    
            DBBINDSTATUS rowBindStatus[2] = {
                DBBINDSTATUS_OK,
                DBBINDSTATUS_OK
            };
            result = rowAccessor->CreateAccessor(
                DBACCESSOR_ROWDATA,
                2,
                rowBindings,
                sizeof(RowData),
                &rowHandle,
                rowBindStatus);
            if (FAILED(result) ||
                rowBindStatus[0] != DBBINDSTATUS_OK ||
                rowBindStatus[1] != DBBINDSTATUS_OK)
            {
                if (SUCCEEDED(result))
                    result = E_FAIL;
                break;
            }
    
            DBCOUNTITEM productsPrinted = 0;
            while (true)
            {
                result = rowset->GetNextRows(
                    DB_NULL_HCHAPTER,
                    0,
                    1,
                    &rowCount,
                    &rows);
                if (FAILED(result) || rowCount == 0)
                    break;
    
                RowData row = {};
                result = rowset->GetData(rows[0], rowHandle, &row);
                if (FAILED(result) ||
                    row.productIdStatus != DBSTATUS_S_OK ||
                    row.productIdLength != sizeof(LONG) ||
                    row.nameStatus != DBSTATUS_S_OK ||
                    row.nameLength == 0 ||
                    row.nameLength % sizeof(wchar_t) != 0 ||
                    row.nameLength >= sizeof(row.name))
                {
                    result = E_FAIL;
                    break;
                }
                row.name[row.nameLength / sizeof(wchar_t)] = L'\0';
    
                if (productsPrinted == 0)
                {
                    std::wcout << L"Connected with MSOLEDBSQL19.\n\n";
                    std::wcout << std::left
                               << std::setw(12) << L"Product ID"
                               << L"Name\n";
                    std::wcout << std::setw(12) << L"----------"
                               << L"----\n";
                }
    
                std::wcout << std::left
                           << std::setw(12) << row.productId
                           << row.name << L'\n';
                ++productsPrinted;
    
                result = rowset->ReleaseRows(
                    rowCount,
                    rows,
                    nullptr,
                    nullptr,
                    nullptr);
                CoTaskMemFree(rows);
                rows = nullptr;
                rowCount = 0;
                if (FAILED(result))
                    break;
            }
    
            if (FAILED(result))
                break;
            if (productsPrinted == 0)
            {
                result = E_FAIL;
                break;
            }
        }
        while (false);
    
        if (rows != nullptr)
        {
            if (rowset != nullptr && rowCount != 0)
                rowset->ReleaseRows(rowCount, rows, nullptr, nullptr, nullptr);
            CoTaskMemFree(rows);
        }
        if (rowHandle != DB_NULL_HACCESSOR && rowAccessor != nullptr)
            rowAccessor->ReleaseAccessor(rowHandle, nullptr);
        if (parameterHandle != DB_NULL_HACCESSOR && parameterAccessor != nullptr)
            parameterAccessor->ReleaseAccessor(parameterHandle, nullptr);
    
        Release(rowAccessor);
        Release(rowset);
        Release(parameterAccessor);
        Release(commandParameters);
        Release(commandText);
        Release(createCommand);
        Release(createSession);
        if (initialized)
            dbInitialize->Uninitialize();
        Release(dbInitialize);
        Release(dataInitialize);
        CoUninitialize();
    
        if (FAILED(result))
        {
            std::wcerr << L"OLE DB operation failed: 0x"
                       << std::hex << result << L'\n';
            return 1;
        }
    
        return 0;
    }
    

连接字符串被传递给 IDataInitialize::GetDataSource。 该 API 使用空格关键字名称 Use Encryption for Data 和 Trust Server Certificate。 连接字符串请求加密并要求证书验证。

查询使用问号作为参数标记。 应用程序将最小产品 ID 0 绑定为 SQL Server int 类型,从 SalesLT.Product 中最多读取五行,并打印产品 ID 和名称。

生成并运行应用程序

  1. 在同一个x64原生工具命令提示符中,找到已安装的OLE DB SDK头,并将其目录存储在 OLEDB_INCLUDE:

    for /f "delims=" %i in ('where /r "%ProgramFiles%\Microsoft SQL Server\Client SDK\OLEDB" msoledbsql.h') do for %j in ("%~dpi.") do set "OLEDB_INCLUDE=%~fj"
    
  2. 显示所选目录,并确认其中包含以下标头:

    echo %OLEDB_INCLUDE%
    dir "%OLEDB_INCLUDE%\msoledbsql.h"
    
  3. 编译应用程序:

    cl /std:c++17 /EHsc /W4 /I"%OLEDB_INCLUDE%" oledb-quickstart.cpp /link ole32.lib oleaut32.lib
    
  4. 运行应用程序:

    oledb-quickstart.exe
    
  5. 从当前命令提示符中清除连接字符串:

    set OLEDB_CONNECTION_STRING=
    set OLEDB_USER_ID=
    set OLEDB_PASSWORD=
    

产品记录会因 AdventureWorksLT 的版本不同而有所变化。 输出如以下示例所示:

Connected with MSOLEDBSQL19.

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