2009-05-13 6 views
0

Je gère une application héritée écrite en MFC/C++. La base de données de l'application est dans SQL Server 2000. Nous avons récemment testé certaines nouvelles fonctionnalités et nous avons constaté que lorsque nous modifions SQL Provider de SQLOLEDB.1 en SQLNCLI.1 du code qui essaie de récupérer des données d'une table via une procédure stockée échoue.La modification du fournisseur SQL de SQLOLEDB.1 à SQLNCLI.1 provoque l'échec de l'application lors de l'accès aux données via la procédure stockée

Le tableau en question est assez simple et a été créé par le script suivant:

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] 

Bascially quatre colonnes, avec TableKey étant une colonne d'identité et tout est peuplé d'autre via le script suivant:

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

avec une longue liste d'autres INSERT INTO suivant celle ci-dessus. Certaines des lignes insérées comportent des caractères spéciaux (comme des accents au-dessus des lettres) dans leurs descriptions. J'avais d'abord pensé que l'inclusion des caractères spéciaux faisait partie du problème mais si j'efface complètement la table et la repeuple alors avec juste le seul INSERT INTO d'en haut qui n'a aucun caractère spécial, il échoue toujours.

donc je suis passé ...

Les données de ce tableau est alors accessible via le code suivant:

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 = ""; 
} 

Le daxLayer est une bibliothèque d'accès aux données tiers l'application utilise, bien que nous avons la source à elle (dont certains on le verra ci-dessous.) SP__GET_ALLERGEN_DESC est la procédure stockée utilisée pour obtenir les données sur la table et il a été créé par ce script:

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 

Lorsque le fournisseur SQL est réglé sur SQLNCLI.1, l'application explose à:

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

de l'extrait de code ci-dessus. Donc, je suis entré dans GetField, qui ressemble à ce qui suit:

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")) 
     } 
    } 
} 

Le coupable ici est:

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

Entrer dans Fields-> GetItem nous amène à:

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); 
} 

Qui nous amène ensuite à:

GetValue

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); 
} 

Si vous regardez _result tout en renforçant par ce lors de l'exécution, la valeur BSTR de _result est correcte, sa valeur est « Egg » du champ « Description » de la table. Continuer à parcourir les traces à travers tous les appels de libération COM, etc.Quand enfin je reviens à:

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

et l'étape passée à la ligne suivante, le contenu de la télévision, qui devrait être BSTR = « Egg » sont maintenant:

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

Lorsque la fonction GetField tente de définir sa valeur de retour à la valeur dans tv.BSTR

_bstr_t bStr = tv; 
sValue = bStr; 

étouffe et meurt sans surprise.

Alors qu'est-il arrivé à la valeur de BSTR et pourquoi cela se produit-il uniquement lorsque le fournisseur est défini sur SQLNCLI.1? Pour le moins, j'ai commenté en utilisant la procédure stockée dans le code le plus haut et juste codé en dur la même instruction SQL SELECT que la procédure stockée utilise et a trouvé que cela fonctionne très bien et la valeur retournée est correcte.

De plus, il est possible pour les utilisateurs d'ajouter des lignes à la table via l'application. Si l'application crée une nouvelle ligne dans cette table et récupère cette ligne via la procédure stockée, elle fonctionne également correctement sauf si vous incluez un caractère spécial dans la description, auquel cas elle enregistre correctement la ligne mais réapparaît de la même manière que ci-dessus lors de la récupération de cette rangée. Pour résumer, si je peux, les lignes placées dans la table via le script INSERT font TOUJOURS sauter l'application quand elles sont accédées par la procédure stockée (qu'elles contiennent ou non des caractères spéciaux). Les lignes placées dans la table à partir de l'application par l'utilisateur lors de l'exécution sont récupérées correctement via la procédure stockée, SAUF si elles contiennent un caractère spécial dans la description, à quel point elles font exploser l'application. Si vous accédez à l'une des lignes de la table en utilisant SQL à partir du code au moment de l'exécution au lieu de la procédure stockée, cela fonctionne, qu'il y ait ou non un caractère spécial dans la description.

Toute lumière qui peut être versé sur ce sera grandement apprécié, et je vous remercie d'avance.

Répondre

1

Cette ligne pourrait être problématique:

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

Si je l'ai lu droit, -> Valeur retourne un _variant_t, qui est un pointeur intelligent. Le pointeur intelligent libère sa variante lorsqu'il sort de la portée, juste après cette ligne. Toutefois, tagVARIANT n'est pas un pointeur intelligent, il n'augmentera donc pas le nombre de références lorsqu'il est affecté à. Donc, après cette ligne, la télévision pourrait pointer vers une variante qui a été effectivement publié. Que se passe-t-il si vous écrivez le code comme ceci?

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

Ou bien, dites-le pointeur intelligent de ne pas libérer sa charge utile:

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

Cela fait longtemps que je codé en C++, et la lecture de ce post, je ne regrette pas se éloigner !

+0

Excellent! Les deux solutions fonctionnent/réparent le problème. Merci beaucoup. J'étais à bout de nerfs, et n'étant qu'un marginal codeur C++ qui apprend encore cela m'a complètement déconcerté. Je suis toujours confus quant à la raison pour laquelle le fournisseur SQL est ce qui a mis cela en lumière, je pense que les problèmes de nombre de référence serait toujours un problème indépendamment du fournisseur SQL. Merci encore! – Scott

+0

Le BSTR sous-jacent à la variante serait valide tant que quelqu'un le référencerait. Peut-être que le nouveau fournisseur est plus agressif quant à la publication de ses propres références au BSTR. En tout cas contente que je puisse aider, je me souviens d'avoir été dans ta position une fois! – Andomar