Я поддерживаю устаревшее приложение, написанное на 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 из кода во время выполнения вместо хранимой процедуры, она работает независимо от того, есть ли в описании специальный символ или нет.
Любой свет, который можно пролить на это, будет высоко оценен, и я заранее благодарю вас.