Изменение поставщика SQL с SQLOLEDB.1 на SQLNCLI.1 приводит к сбою приложения при доступе к данным через хранимую процедуру - PullRequest
0 голосов
/ 13 мая 2009

Я поддерживаю устаревшее приложение, написанное на MFC / C ++. База данных для приложения находится в SQL Server 2000. Недавно мы включили некоторые новые функции и обнаружили, что при изменении поставщика SQL с SQLOLEDB.1 на SQLNCLI.1 появляется некоторый код, который пытается получить данные из таблицы с помощью хранимой процедуры. терпит неудачу.

Данная таблица довольно проста и была создана с помощью следующего скрипта:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[UAllergenText](
    [TableKey] [int] IDENTITY(1,1) NOT NULL,
    [GroupKey] [int] NOT NULL,
    [Description] [nvarchar](150) NOT NULL,
    [LanguageEnum] [int] NOT NULL,
CONSTRAINT [PK_UAllergenText] PRIMARY KEY CLUSTERED
(
    [TableKey] ASC) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF,
    IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
ALTER TABLE [dbo].[UAllergenText]  WITH CHECK ADD  CONSTRAINT 
FK_UAllergenText_UBaseFoodGroupInfo] FOREIGN KEY([GroupKey])
REFERENCES [dbo].[UBaseFoodGroupInfo] ([GroupKey])
GO
ALTER TABLE [dbo].[UAllergenText] CHECK CONSTRAINT 
FK_UAllergenText_UBaseFoodGroupInfo]

В основном четыре столбца, где TableKey является столбцом идентификаторов, а все остальное заполняется с помощью следующего скрипта:

INSERT INTO UAllergenText (GroupKey, Description, LanguageEnum)
VALUES (401, 'Egg', 1)

с длинным списком других INSERT INTO, которые следуют за вышеупомянутым. Некоторые из вставленных строк имеют специальные символы (например, знаки ударения над буквами) в своих описаниях. Первоначально я думал, что включение специальных символов было частью проблемы, но если я полностью очищу таблицу, а затем снова заполню ее только одним INSERT INTO сверху, который не имеет специальных символов, он все равно завершится неудачей.

Итак, я пошел дальше ...

Доступ к данным в этой таблице осуществляется с помощью следующего кода:

std::wstring wSPName = SP_GET_ALLERGEN_DESC;
_variant_t  vtEmpty1 (DISP_E_PARAMNOTFOUND, VT_ERROR);
_variant_t  vtEmpty2(DISP_E_PARAMNOTFOUND, VT_ERROR);

_CommandPtr pCmd = daxLayer::CDataAccess::GetSPCommand(pConn, wSPName); 
pCmd->Parameters->Append(pCmd->CreateParameter("@intGroupKey", adInteger, adParamInput, 0, _variant_t((long)nGroupKey)));
pCmd->Parameters->Append(pCmd->CreateParameter("@intLangaugeEnum", adInteger, adParamInput, 0, _variant_t((int)language)));

_RecordsetPtr pRS = pCmd->Execute(&vtEmpty1, &vtEmpty2, adCmdStoredProc);            

//std::wstring wSQL = L"select Description from UAllergenText WHERE GroupKey = 401 AND LanguageEnum = 1";
//_RecordsetPtr pRS = daxLayer::CRecordsetAccess::GetRecordsetPtr(pConn,wSQL);

if (pRS->GetRecordCount() > 0)
{
    std::wstring wDescField = L"Description";
    daxLayer::CRecordsetAccess::GetField(pRS, wDescField, nameString);
}   
else
{
    nameString = "";
}

daxLayer - это сторонняя библиотека доступа к данным, которую использует приложение, хотя у нас есть источник к ней (некоторые из которых будут рассмотрены ниже.) SP__GET_ALLERGEN_DESC - это хранимый процесс, используемый для извлечения данных из таблицы, и он был создан с помощью этого скрипта:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [dbo].[spRET_AllergenDescription] 
-- Add the parameters for the stored procedure here
    @intGroupKey int, 
    @intLanguageEnum int
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Insert statements for procedure here
    SELECT Description FROM UAllergenText WHERE GroupKey = @intGroupKey AND LanguageEnum = @intLanguageEnum
END

Когда для поставщика SQL установлено значение SQLNCLI.1, приложение взрывается по адресу:

daxLayer::CRecordsetAccess::GetField(pRS, wDescField, nameString);

из приведенного выше фрагмента кода. Поэтому я вошел в GetField, который выглядит следующим образом:

void daxLayer::CRecordsetAccess::GetField(_RecordsetPtr pRS,
const std::wstring wstrFieldName, std::string& sValue, std::string  sNullValue)
{
    if (pRS == NULL)
    {
        assert(false);
        THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField", 
        wstrFieldName, L"std::string", L"Missing recordset pointer."))
    }
    else
    {
        try
        {
            tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;

            if ((tv.vt == VT_EMPTY) || (tv.vt == VT_NULL))
            {
                sValue = sNullValue;
            }
            else if (tv.vt != VT_BSTR)
            {
                // The type in the database is wrong.
                assert(false);
                THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField", 
                wstrFieldName, L"std::string", L"Field type is not string"))
            }
            else
            {
                 _bstr_t bStr = tv ;//static_cast<_bstr_t>(pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value);                     
                 sValue = bStr;
            }
        }
        catch( _com_error &e )
        {
            RETHROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField", 
            wstrFieldName, L"std::string"), e.Description())
        }
        catch(...)
        {        
            THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
            wstrFieldName, L"std::string", L"Unknown error"))
        }
    }
}

Виновник здесь:

tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;

Вступая в поля-> GetItem приводит нас к:

GetItem

inline FieldPtr Fields15::GetItem ( const _variant_t & Index ) {
    struct Field * _result = 0;
    HRESULT _hr = get_Item(Index, &_result);
    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
    return FieldPtr(_result, false);
}

Что приводит нас к:

ПолучитьЗначение

inline _variant_t Field20::GetValue ( ) {
    VARIANT _result;
    VariantInit(&_result);
    HRESULT _hr = get_Value(&_result);
    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
    return _variant_t(_result, false);
}

Если вы посмотрите на _result, шагая по нему во время выполнения, значение BSTR _result будет правильным, его значение будет равно «Egg» из поля «Description» таблицы. Продолжая перебирать следы через все вызовы освобождения COM и т. Д. Когда я наконец вернусь к:

tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;

И перейдите к следующей строке, содержимое тв, которое должно быть BSTR = "Яйцо", теперь:

tv BSTR = 0x077b0e1c "ᎀݸﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮ㨼㺛帛᠄"

Когда функция GetField пытается установить свое возвращаемое значение равным значению в tv.BSTR

_bstr_t bStr = tv;
sValue = bStr;

неудивительно, что он задыхается и умирает.

Так что же случилось со значением BSTR и почему это происходит только тогда, когда для провайдера установлено значение SQLNCLI.1?

Черт возьми, я прокомментировал использование хранимой процедуры в самом верхнем коде и просто жестко запрограммировал ту же инструкцию SQL SELECT, которую использует хранимая процедура, и обнаружил, что она работает нормально, и возвращаемое значение верное.

Кроме того, пользователи могут добавлять строки в таблицу через приложение. Если приложение создает новую строку в этой таблице и извлекает эту строку с помощью хранимой процедуры, оно также работает правильно, если только вы не включите в описание специальный символ, в этом случае оно корректно сохраняет строку, но снова взрывается точно так же, как описано выше. после извлечения этого ряда.

Таким образом, чтобы подвести итог, если можно, строки, помещенные в таблицу с помощью сценария INSERT, ВСЕГДА взрывают приложение, когда к ним обращается хранимая процедура (независимо от того, содержат ли они какие-либо специальные символы). Строки, помещенные в таблицу пользователем из приложения во время выполнения, корректно извлекаются с помощью хранимой процедуры, ЕСЛИ они не содержат специальный символ в описании, после чего они взрывают приложение. Если вы обращаетесь к любой из строк в таблице, используя SQL из кода во время выполнения вместо хранимой процедуры, она работает независимо от того, есть ли в описании специальный символ или нет.

Любой свет, который можно пролить на это, будет высоко оценен, и я заранее благодарю вас.

1 Ответ

1 голос
/ 13 мая 2009

Эта строка может быть проблемной:

tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;

Если я правильно понял, -> Value возвращает _variant_t, который является умным указателем. Умный указатель выпустит свой вариант, когда он выйдет из области видимости, сразу после этой строки. Однако tagVARIANT не является умным указателем, поэтому он не будет увеличивать количество ссылок, когда ему назначено. Поэтому после этой строки телевизор может указывать на вариант, который был фактически выпущен.

Что произойдет, если вы напишите такой код?

_variant_t tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;

Или же, скажите умному указателю не освобождать его полезную нагрузку:

_tagVARIANT tv = pRS->Fields->GetItem(
    _variant_t(wstrFieldName.c_str()))->Value.Detach();

Прошло много времени с тех пор, как я написал код на C ++, и, читая этот пост, я не жалею об уходе!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...