Notice: This website is an unofficial Microsoft Knowledge Base (hereinafter KB) archive and is intended to provide a reliable access to deleted content from Microsoft KB. All KB articles are owned by Microsoft Corporation. Read full disclaimer for more details.

BUG: SQLBulkOperations and SQLSetPos Don't Insert Identity Values


View products that this article applies to.

Symptoms

When you use the SQLBulkOperations or SQLSetPos ODBC call to insert explicit values into a SQL Server identity column, the call may fail with the following error:
DIAG [37000] [Microsoft][ODBC SQL Server Driver][SQL Server]Explicit value must be specified for identity column in table 'new_employees' when IDENTITY_INSERT is set to ON. (545)

↑ Back to the top


Cause

The ODBC SQL Server driver does not send the bound information for the identity column to the server for insertion.

↑ Back to the top


Resolution

Do not use SQLBulkOperations or SQLSetPos to insert data into SQL Server identity columns. Instead, consider using parameterized INSERT statements with SQLPrepare and SQLExecute.

↑ Back to the top


Status

Microsoft has confirmed that this is a bug in the Microsoft products that are listed at the beginning of this article.

↑ Back to the top


More information

Steps to Reproduce Behavior

  1. Create a table using the following schema:
    create table new_employees (
    	id_num int identity(1,1),
    	fname char(20),
    	minit char(1),
    	lname char(30)
    )
    					
  2. Create a new Microsoft Visual C++ Console application and paste the following code:
    #include <windows.h>
    #include <sql.h>
    #include <sqlext.h>
    #include <tchar.h>
    #include <iostream.h>
    #include <stdio.h>
    
    typedef struct {
    	SQLUINTEGER  id_num;
    	SQLINTEGER   id_num_LI;
    	SQLCHAR      fname[21];
    	SQLINTEGER   fname_LI;
    	SQLCHAR      minit[2];
    	SQLINTEGER   minit_LI;
    	SQLCHAR      lname[31];
    	SQLINTEGER   lname_LI;
    } EMP_INFO;
    
    EMP_INFO EmpInfoArray[2];
    
    void OutputMessages(SQLSMALLINT handletype, SQLHANDLE* handlepointer) 
    {
    	
    	int looper = 1;
    	SQLCHAR state[10];
    	SQLCHAR message[100];
    	SQLINTEGER nativeError;
    	
    	while (SQL_NO_DATA != SQLGetDiagRec(handletype, *handlepointer, looper, state, &nativeError,
    		message, 100, NULL)) {
    		//cout<<"ERROR["<<looper<<"]::STATE["<<state<<"]::MESSAGE: \""<<message<<"\""<<endl;
    		printf("ERROR[%i]::STATE[%s]::MESSAGE: \"%s (%d)\"\n", looper, state, message, nativeError);
    		looper++;
    	}
    	
    }
    
    void main()
    {
    	SQLRETURN ret;
    	SQLHENV henv1;
    	SQLHDBC hdbc1;
    	SQLHSTMT hstmt1;
    	SQLPOINTER sqlp = NULL;
    	
    	SQLUSMALLINT  RowStatusArray[12];
    	SQLCHAR       SQLStmt[255];
    	
    	ret = SQLAllocEnv(&henv1);
    	
    	ret = SQLAllocConnect(henv1, &hdbc1);
    	
    	ret = SQLDriverConnect(hdbc1, NULL, (SQLCHAR*)"DSN=reidwpridsn;UID=sa;PWD=;", SQL_NTS,
    		NULL, SQL_NTS, NULL, SQL_DRIVER_NOPROMPT);
    	
    	ret = SQLAllocStmt(hdbc1, &hstmt1);
    	
    	ret = SQLSetStmtAttr(hstmt1, SQL_ATTR_CONCURRENCY, 
    		(SQLPOINTER) SQL_CONCUR_ROWVER, 0);
    	
    	
        ret = SQLSetStmtAttr(hstmt1, SQL_ATTR_CURSOR_TYPE, 
    		(SQLPOINTER) SQL_CURSOR_DYNAMIC, 0);
    	
    	
        // Use row-wise binding.
        ret = SQLSetStmtAttr(hstmt1, SQL_ATTR_ROW_BIND_TYPE,
    		(SQLPOINTER) sizeof(EMP_INFO), 0);
    	
    	
    	ret = SQLSetStmtAttr(hstmt1, SQL_ATTR_ROW_ARRAY_SIZE, 
    		(SQLPOINTER) 1, 0);
    	
    	ret = SQLSetStmtAttr(hstmt1, SQL_ATTR_ROW_STATUS_PTR, 
    		RowStatusArray, 0);
    	
    	strcpy((char *) SQLStmt, "SET IDENTITY_INSERT new_employees ON");
    	
    	ret = SQLExecDirect(hstmt1, SQLStmt, SQL_NTS);
    	
    	strcpy((char *) SQLStmt, "SELECT id_num, fname, minit, lname FROM new_employees");
    	
    	ret = SQLExecDirect(hstmt1, SQLStmt, SQL_NTS);
    	
    	ret = SQLBindCol(hstmt1, 1, SQL_C_SLONG, 
    		&EmpInfoArray[0].id_num, sizeof(EmpInfoArray[0].id_num),
    		&EmpInfoArray[0].id_num_LI);
    	
    	
        ret = SQLBindCol(hstmt1, 2, SQL_C_CHAR,   
    		EmpInfoArray[0].fname, 
    		sizeof(EmpInfoArray[0].fname),
    		&EmpInfoArray[0].fname_LI);
    	
    	
        ret = SQLBindCol(hstmt1, 3, SQL_C_CHAR, 
    		&EmpInfoArray[0].minit,
    		sizeof(EmpInfoArray[0].minit),
    		&EmpInfoArray[0].minit_LI);
    	
    	
        ret = SQLBindCol(hstmt1, 4, SQL_C_CHAR,   
    		EmpInfoArray[0].lname, 
    		sizeof(EmpInfoArray[0].lname),
    		&EmpInfoArray[0].lname_LI);
    	
    	ret = SQLFetchScroll(hstmt1, SQL_FETCH_NEXT, 0);
    	
    	
    	EmpInfoArray[0].id_num = 200;
    	EmpInfoArray[0].id_num_LI = SQL_NTS;
    	strcpy((char *) EmpInfoArray[0].lname, "TEST");
    	EmpInfoArray[0].lname_LI = SQL_NTS;
    	strcpy((char *) EmpInfoArray[0].fname, "FNAME");
    	EmpInfoArray[0].fname_LI = SQL_NTS;
    	strcpy((char *) EmpInfoArray[0].minit, "C");
    	EmpInfoArray[0].minit_LI = SQL_NTS;
    
    	
       // The following line generates the error.
       ret = SQLBulkOperations(hstmt1, SQL_ADD);
       // Alternatively, you can use the following line and you will get the same error.
       //ret = SQLSetPos(hstmt1, 0, SQL_ADD, SQL_LOCK_NO_CHANGE);
       
       if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) 
    	   cout << "success" <<endl;
       else
    	   OutputMessages(SQL_HANDLE_STMT, &hstmt1);
       
       ret = SQLCloseCursor(hstmt1);
       
       strcpy((char *) SQLStmt, "SET IDENTITY_INSERT new_employees OFF");
       
       ret = SQLExecDirect(hstmt1, SQLStmt, SQL_NTS);
       
       SQLFreeStmt(hstmt1, SQL_DROP);
       
       SQLDisconnect(hdbc1);
       
       SQLFreeConnect(hdbc1);
       
    
       SQLFreeEnv(henv1);
    }
    					
  3. Compile and then run the application.

↑ Back to the top


Article Info
Article ID : 294153
Revision : 3
Created on : 1/1/0001
Published on : 1/1/0001
Exists online : False
Views : 243