【问题标题】:SQL Server ODBC Driver not raising errorsSQL Server ODBC 驱动程序未引发错误
【发布时间】:2016-12-12 09:31:35
【问题描述】:

这个问题是 Microsoft ODBC 中一系列错误的一部分 司机:

微软已经表示他们不会在他们的 ODBC 中修复这些错误 司机。

背景

如果我有一个示例表:

CREATE TABLE Wallet (
    WalletID int NOT NULL,
    Name varchar(50) NOT NULL
)

我尝试发出插入到表中的 sql,但没有为 NOT NULL WalletID 列指定值:

INSERT INTO Wallet (WalletID, Name) VALUES (1, 'Fan')
INSERT INTO Wallet (Name) VALUES ('Ardent Defender') --Constraint violation

SQL Server 报错:

(1 行受影响)
消息 515,第 16 级,状态 2,第 2 行
无法将值 NULL 插入列“WalletID”、表“Scratch.dbo.Wallet”;列不允许空值。插入失败。
该语句已终止。

这是有道理的。

使用 ADO/OLEDB 和 SQL Server OLE DB 提供程序 (SQLOLEDB) 连接时:

Provider=SQLOLEDB;Data Source=hyperion;User ID=Contoso;Password=Trub4dor;

并且我执行了INSERT,ADO/OLEDB/COM 基础结构返回一个失败,作为异常返回到高级语言:

无法将值 NULL 插入到列 'WalletID'、表 'Wallet' 中;列不允许空值。插入失败

这一切都说得通。

但请尝试使用 ODBC 驱动程序

由于 本机客户端 OLE DB 提供程序(和the MS recommendation that you do not use the native client ODBC drivers)的贬低,我想我会尝试使用 SQL Server ODBC 驱动程序:

Provider=MSDASQL;Driver={SQL Server};Server={hyperion};UID={Contoso};PWD={Trub4dor};


更新 - 不推荐使用:六年后,Microsoft 有了announced the un-deprecation of OLE DB support for SQL Server,并为 SQL Server 创建了第三个 OLE DB 驱动程序:msoledbsql。 (archive)

以前,Microsoft announced deprecation of the Microsoft OLE DB Provider for SQL Server,是 SQL Server Native Client (SNAC) 的一部分。当时,我们做出这个决定是为了在我们使用 Azure SQL 数据库进入云时代时,尝试为围绕 Windows 原生软件开发的开发人员故事提供更多的简单性,并尝试为开发人员利用 JDBC 和 ODBC 的相似性。然而,在随后的审查中,确定弃用是一个错误,因为 SQL Server 中的大量场景仍然依赖于 OLE DB,并且更改这些场景会破坏一些现有的客户场景。

考虑到这一点,我们决定弃用 OLE DB,并在 2018 日历年第一季度 2018 年 3 月之前发布新版本。


我发出我的批次:

INSERT INTO Wallet (WalletID, Name) VALUES (1, 'Fan')
INSERT INTO Wallet (Name) VALUES ('Ardent Defender')

我惊讶地发现同样的 SQL 语句:

  • 在 SQL Server 本身中触发 INSERT FAILS 错误:

  • 在使用 SQL Server OLE DB 提供程序时导致客户端错误

在使用 ODBC 驱动程序时会静默失败。该语句执行时不会引发任何错误。

当我的 SQL 语句运行没有错误时,我困惑了大约一个小时,但是这些行不会出现在数据库中。

静默失败

显然,无声的失败是不好的。

但是发生了什么?

我如何告诉 ADO-OLEDB-ODBC 驱动程序报告错误。它是 ODBC 连接字符串设置吗?是 MSDASQL 连接字符串设置吗?

示例伪代码

我实际上将 Delphi 与 ADO 一起使用。但我会将其转码为伪 C# 样式代码,以便于概念理解。

String commandText = 
      "INSERT INTO Wallet (WalletID, Name) VALUES (1, 'Fan')"+CRLF+
      "INSERT INTO Wallet (Name) VALUES ('Ardent Defender')";

ADOConnection conn = new ADOConnection();
conn.ConnectionString = szConnectionString;
conn.Open();

HRESULT hr = conn.Execute(commandText, ref recordsAffected, [eoExecuteNoRecords]);

实际上,HRESULT 的检查是由语言基础结构和编译器魔法处理的 - 如果 FAILED 会引发本地语言异常。

【问题讨论】:

  • 是你没有设置接收错误信息的命令的问题吗?
  • 可以贴一下应用代码sn-p吗?我无法复制。
  • 请显示一些最小代码。我有测试用例明确测试这样的失败,一切都按预期工作(使用 sql server 2014 和本机客户端驱动程序,在 windows 上使用 c++)。
  • @erg 找出了重要的部分:它必须是批处理中的多个语句,随后的语句失败。
  • @DanGuzman 我发现了使人为测试失败的缺失技巧:第一个语句不会失败的多个语句。

标签: sql-server sql-server-2008-r2 odbc oledb ado


【解决方案1】:

除非您指定SET NOCOUNT ON,否则SQL Server 在每条语句之后都会向客户端返回DONE_IN_PROC 消息。这些消息(行计数)将影响 ADO 应用程序,这些应用程序未编码为处理调用NextRecordset 方法返回的多个记录集。

简单的解决方案是将SET NOCOUNT ON 指定为批处理或存储过程中的第一条语句。

【讨论】:

  • 确实,这似乎是执行多条语句的正确方式。快速测试表明,如果设置了SET NOCOUNT ON,则语句的执行将失败并报告正确的错误消息。
  • 如果是这样的话;为什么它与 OLEDB 驱动程序一起工作,它还在每个语句之后发回 "count" 记录集。
【解决方案2】:

我没有仔细阅读您的问题,因此我的评论提到了错误的 ODBC 驱动程序。

无论如何,我针对 SQL Server 2014 使用三种不同的 ODBC 驱动程序对其进行了测试:

  • SQL Server Native Client 11.0
  • 适用于 SQL Server 的 ODBC 驱动程序 11
  • SQL 服务器

更新:对于所有三个驱动程序,如果我只执行一个插入,我会收到以下形式的预期错误:

错误;本机:515;状态:23000;味精:[Microsoft][ODBC SQL Server Driver][SQLServer]无法将值 NULL 插入到列 'WalletID',表 'Test.dbo.Walle t';列不允许空值。插入失败。 错误;本机:3621;状态:01000; msg: [Microsoft][ODBC SQL Server Driver][SQL Server]语句已终止。

如果我在一个语句中执行多个插入,并且第一个插入没有失败,则不会引发错误,如 Ian Boyd 的评论中所述。

解决方法:关闭自动提交,但启用手动提交。多次插入的执行仍然不会失败,但提交事务会引发错误。不幸的是,我无法检索到任何错误信息,但至少您在尝试提交时会得到一个SQL_ERROR

这是更新后的示例代码:

#include <windows.h>
#include <tchar.h>
#include <iostream>
#include <sql.h>
#include <sqlext.h>
#include <sqlucode.h>
#include <functional>

void printErr(SQLHANDLE handle, SQLSMALLINT handleType)
{
    SQLSMALLINT recNr = 1;
    SQLRETURN ret = SQL_SUCCESS;
    while (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)
    {
        SQLWCHAR errMsg[SQL_MAX_MESSAGE_LENGTH + 1];
        SQLWCHAR sqlState[5 + 1];
        errMsg[0] = 0;
        SQLINTEGER nativeError;
        SQLSMALLINT cb = 0;
        ret = SQLGetDiagRec(handleType, handle, recNr, sqlState, &nativeError, errMsg, SQL_MAX_MESSAGE_LENGTH + 1, &cb);
        if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)
        {
            std::wcerr << L"ERROR; native: " << nativeError << L"; state: " << sqlState << L"; msg: " << errMsg << std::endl;
        }
        ++recNr;
    }
}


int _tmain(int argc, _TCHAR* argv[])
{
    SQLHSTMT stmt = SQL_NULL_HSTMT;

    SQLRETURN   nResult = 0;
    SQLHANDLE   handleEnv = 0;

    nResult = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, (SQLHANDLE*)&handleEnv);
    nResult = SQLSetEnvAttr(handleEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3_80, SQL_IS_INTEGER);

    SQLHANDLE   handleDBC = 0;
    nResult = SQLAllocHandle(SQL_HANDLE_DBC, handleEnv, (SQLHANDLE*)&handleDBC);

//  SQLWCHAR     strConnect[256] = L"Driver={SQL Server Native Client 11.0};Server=.\\TEST;Database=Test;Trusted_Connection=yes;";
//  SQLWCHAR     strConnect[256] = L"Driver={ODBC Driver 11 for SQL Server};Server=.\\TEST;Database=Test;Trusted_Connection=yes;";
    SQLWCHAR     strConnect[256] = L"Driver={SQL Server};Server=.\\TEST;Database=Test;Trusted_Connection=yes;";
    SQLWCHAR     strConnectOut[1024] = { 0 };
    SQLSMALLINT nNumOut = 0;
    nResult = SQLDriverConnect(handleDBC, NULL, (SQLWCHAR*)strConnect, SQL_NTS, (SQLWCHAR*)strConnectOut, sizeof(strConnectOut),
        &nNumOut, SQL_DRIVER_NOPROMPT);
    if (!SQL_SUCCEEDED(nResult))
    {
        printErr(handleDBC, SQL_HANDLE_DBC);
    }

    // Enable manual commit
    nResult = SQLSetConnectAttr(handleDBC, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
    if (!SQL_SUCCEEDED(nResult))
    {
        printErr(handleDBC, SQL_HANDLE_DBC);
    }

    SQLHSTMT    handleStatement = 0;
    nResult = SQLAllocHandle(SQL_HANDLE_STMT, handleDBC, (SQLHANDLE*)&handleStatement);
    if (!SQL_SUCCEEDED(nResult))
    {
        printErr(handleDBC, SQL_HANDLE_DBC);
    }

    // try to drop, ignore if it exists
    SQLExecDirect(handleStatement, L"DROP TABLE Wallet", SQL_NTS);

    nResult = SQLExecDirect(handleStatement, L"CREATE TABLE Wallet (WalletID int NOT NULL,  Name varchar(50) NOT NULL)", SQL_NTS);
    if (!SQL_SUCCEEDED(nResult))
    {
        printErr(handleStatement, SQL_HANDLE_STMT);
    }

    //nResult = SQLExecDirect(handleStatement, L"INSERT INTO Wallet (WalletID, Name) VALUES (2, 'Fan') INSERT INTO Wallet (WalletID, Name) VALUES (1, 'Fan')", SQL_NTS);
    nResult = SQLExecDirect(handleStatement, L"INSERT INTO Wallet (WalletID, Name) VALUES (2, 'Fan') INSERT INTO Wallet (Name) VALUES ('Fan')", SQL_NTS);
    //nResult = SQLExecDirect(handleStatement, L"INSERT INTO Wallet (Name) VALUES ('Fan')", SQL_NTS);
    //nResult = SQLExecDirect(handleStatement, L"INSERT INTO Wallet (WalletID, Name) VALUES (1, 'Fan')", SQL_NTS);
    if (!SQL_SUCCEEDED(nResult))
    {
        printErr(handleStatement, SQL_HANDLE_STMT);
    }

    // Try to end transaction. Will fail, but no error information can be fetched(?)
    nResult = SQLEndTran(SQL_HANDLE_DBC, handleDBC, SQL_COMMIT);
    if (!SQL_SUCCEEDED(nResult))
    {
        printErr(handleDBC, SQL_HANDLE_DBC);
    }

    return 0;
}

【讨论】:

    【解决方案3】:

    WalletID int 列未设置为自动递增。

    【讨论】:

    • 我知道。我正在演示一个失败,并使用约束违规作为获得最小可重现示例的手段。
    猜你喜欢
    • 2018-07-26
    • 2022-08-18
    • 2022-06-15
    • 1970-01-01
    • 2022-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多