SQL Server ODBC驱动程序不会引发错误

时间:2016-08-05 21:45:52

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

  

此问题是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行受影响)
  Msg 515,Level 16,State 2,Line 2
  无法将值NULL插入列' WalletID',table' Scratch.dbo.Wallet&#39 ;;列不允许空值。 INSERT失败。
  声明已经终止。

这是有道理的。

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

  

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

我执行 INSERT ,ADO / OLEDB / COM基础设施返回失败,作为例外,它返回高级语言:

  

无法将值NULL插入列'电子钱包ID',表'电子钱包&#39 ;;列不允许空值。 INSERT失败

这一切都有道理。

但是尝试使用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年3月的第一季度发布新版本。

我发出我的批次:

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

我很惊讶地得知同样的SQL语句:

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

    enter image description here

  • 在使用SQL Server OLE DB提供程序

  • 时导致客户端错误
使用ODBC驱动程序时

静默失败。该语句执行时不会引发任何错误。

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

无声失败

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

但是发生了什么?

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

样本Psuedocode

我实际上正在使用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 则抛出本机语言异常。

3 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:2)

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

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

  • SQL Server Native Client 11.0
  • 用于SQL Server的ODBC驱动程序11
  • SQL Server

更新:对于所有三个驱动程序,如果我只执行一个插入,我会在表单中收到预期的错误:

  

ERROR;土生土长:515;州:23000; msg:[Microsoft] [ODBC SQL Server驱动程序] [SQLServer]无法将值NULL插入列' WalletID',table' Test.dbo.Walle t&#39 ;;列不允许空值。 INSERT失败。   错误;土生土长:3621;州:01000; msg:[Microsoft] [ODBC SQL Server驱动程序] [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;
}

答案 2 :(得分:0)

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

enter image description here