Изменение поставщика SQL с SQLOLEDB.1 на SQLNCLI.1 приводит к сбою приложения при доступе к данным через хранимую процедуру
Я поддерживаю устаревшее приложение, написанное на 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;
И перейдите к следующей строке, содержимое TV, которое должно быть BSTR="Egg", теперь:
tv BSTR = 0x077b0e1c "ᎀݸﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮ㨼㺛帛᠄"
Когда функция GetField пытается установить свое возвращаемое значение равным значению в tv.BSTR
_bstr_t bStr = tv;
sValue = bStr;
неудивительно, что он задыхается и умирает.
Так что же случилось со значением BSTR и почему это происходит только тогда, когда для провайдера установлено значение SQLNCLI.1?
Для этого я прокомментировал использование хранимой процедуры в самом верхнем коде и просто жестко запрограммировал ту же инструкцию SQL SELECT, которую использует хранимая процедура, и обнаружил, что она работает просто отлично, а возвращаемое значение верное.
Кроме того, пользователи могут добавлять строки в таблицу через приложение. Если приложение создает новую строку в этой таблице и извлекает эту строку с помощью хранимой процедуры, оно также работает правильно, если только вы не включите в описание специальный символ, и в этом случае оно корректно сохраняет строку, но снова взрывается точно так же, как описано выше. после извлечения этого ряда.
Итак, суммируя, если можно, строки, помещенные в таблицу с помощью сценария INSERT, ВСЕГДА взрывают приложение, когда к ним обращается хранимая процедура (независимо от того, содержат ли они какие-либо специальные символы). Строки, помещенные в таблицу пользователем из приложения во время выполнения, корректно извлекаются с помощью хранимой процедуры, ЕСЛИ они не содержат специальный символ в описании, после чего они взрывают приложение. Если вы обращаетесь к любой из строк в таблице, используя SQL из кода во время выполнения вместо хранимой процедуры, она работает независимо от того, есть ли в описании специальный символ или нет.
Любой свет, который можно пролить на это, будет высоко оценен, и я заранее благодарю вас.
1 ответ
Эта строка может быть проблематичной:
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++, и, читая этот пост, я не жалею об уходе!