IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Bases de données Delphi Discussion :

[BDE & Delphi 2009] : je ne peux plus insérer de champ BLOB ?


Sujet :

Bases de données Delphi

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut [BDE & Delphi 2009] : je ne peux plus insérer de champ BLOB ?
    Bonjour à tous,

    Je dois passer une application Delphi7 en Delphi 2009. Le plus gros souci va concerner l'Unicode mais déjà je rencontre un problème sur l'insertion de valeurs binaires (type blob) en base de données.

    Nous utilisons le BDE avec le composant 'TQuery', en créant des requêtes paramétrées pour l'insertion de champ de type 'blob'. La contenu du paramètre est chargé via un appel à 'LoadFromStream' ou 'LoadFromFile'.
    Cela fonctionnait très bien sous Delphi 7, le type de donnée du paramètre était alors 'ftBlob' tandis que le contenu du variant sous-jacent (propriété 'Value') avait un type = 'varString'. La méthode 'AsBlob' du paramètre retournant le bon contenu (un 'string' contenant contenant les données du 'TStream' ou du fichier initial), la propriété Value retournant la même chose à une différence prêt : celui-ci est tronqué au premier caractère #0. Cela fonctionnait très bien (c'est le contenu retourné par 'AsBlob' qui était effectivement inséré en base de données).

    Depuis le passage à Delphi 2009 j'ai noté les différences suivantes avec le même code :
    - Le contenu du variant sous-jacent possède désormais le type 'Array of Byte'
    - La méthode 'AsBlob' retourne un TBytes (array of byte) correspondant au contenu du 'TStream' ou du fichier initial.
    - La propriété 'Value' retourne la même chose que 'AsBlob'.
    => tout semble donc être normal, étant donné le support de l'Unicode dans Delphi 2009 il semble logique que les méthodes de chargement de contenu binaire (LoadFromStream ou LoadFromFile) utilisent le type 'TBytes' (array of byte) plutôt que 'string'.

    ==> là où ça se gâte c'est qu'à l'exécution de la requête le contenu du paramètre n'est pas inséré en base de données, à la place mon champ contient '€€€€€€€€€€€€€€€€€€€....', bref un contenu corrompu.

    Je ne vois pas d'où cela peut provenir puisque ma requête est bien formulée ('Update License Set LICENSEDATA = :MYPARAM Where IDLICENSE = 19') et mon paramètre est correctement défini avant l'appel à ExecSQL. Et le code fonctionnait très bien sous Delphi 7.

    J'ai installé toutes les mises à jour possible de Delphi2009 mais cela n'a rien changé.

    Pouvez-vous m'aider à résoudre ce problème ? Merci !

  2. #2
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 455
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 455
    Points : 24 867
    Points
    24 867
    Par défaut
    Tu pourrais donner un exemple de code ?
    As-tu utilisé RawByteString à la place des anciens String si tu utilises AsString (ce qui est tout de même une vilaine tricherie)
    Ou as-tu systèmatiquement utilisé un Stream, ce qui est de loin la meilleure méthode !
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par ShaiLeTroll Voir le message
    Tu pourrais donner un exemple de code ?
    As-tu utilisé RawByteString à la place des anciens String si tu utilises AsString (ce qui est tout de même une vilaine tricherie)
    Ou as-tu systèmatiquement utilisé un Stream, ce qui est de loin la meilleure méthode !
    J'utilise systématiquement un TStream, voici la méthode qui est appelée pour créér le paramètre avant l'exécution de la requête d'insertion :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    function InternalAddParam( AQuery: TQuery; AFieldType: TDiaDBFieldType; const ParamName: string;
                               ParamValue: variant; AAutoClearParams: Boolean=False ): TParam;
    var
       MyStream: TMemoryStream;
    begin
       Assert( AQuery <> nil );
     
       if ( AAutoClearParams ) then begin
          AQuery.Params.Clear;
          Result:= nil;
     
       end else Result:= AQuery.Params.FindParam( ParamName );
     
       if ( Result = nil ) then
          Result:= AQuery.Params.CreateParam( DiaDBFieldTypeToFieldType( AFieldType ), ParamName, ptInput );
     
       Assert( Result <> nil );
     
       if ( AFieldType = ddbftBinary ) then begin
          MyStream:= TMemoryStream.Create;
          try
             GetDataAsStreamFromVariant( ParamValue, MyStream );
             Result.LoadFromStream( MyStream, ftBlob );
          finally
             MyStream.Free;
          end;
       end else Result.Value:= ParamValue;
    end;
    Pour information le contenu variant passé en argument est de type 'array of byte', si je passe par un stream lors de l'utilisation d'un champ binaire (ici 'ddbftBinary' est équivalent à 'ftblob') c'est parce-que sans cela sous Delphi7 le contenu du paramètre n'était pas bien affecté en utilisant directement 'Result.Value:= ParamValue;' (le variant obtenu était de type 'string' et les données situés après le premier #0 étaient tronquées). Ce qui ne semble d'ailleurs plus être le cas depuis Delphi 2009, mais j'ai laissé l'implémentation telle quelle pour le moment...

    Voici le code de l'exécution de la requête en elle-même :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
          AQuery.SQL.Text:= 'Insert Into LICENSE ( ' +
                            Cst_Bdd_LicenseCreationDate + ', ' +
                            Cst_Bdd_LicenseUserName + ', ' +
                            Cst_Bdd_LicenseData + ' ) Values ( ' +
                            Dts_FormatDateTime( FInsertDate ) + ', ' +
                            Dts_FormatString( AUserName ) + ', ' +
                            AddParamToQuery( AQuery, Cst_Bdd_LicenseData, VarValue,
                                             ddbftBinary, rbValue ) + ' )';
          // Pourquoi cela ne fonctionne-t-il pas ?
          // Le contenu du paramètre semble OK, la requête aussi, mais
          // son exécution me retourne un champ contenant que des '€€€€' sous
          // Delphi 2009 ???
          // Le même code fonctionne parfaitement sous Delphi 7...
          AQuery.ExecSQL;

  4. #4
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 455
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 455
    Points : 24 867
    Points
    24 867
    Par défaut
    Bon, tu as des fonctions et types persos Dia... cela n'aide pas à comprendre !
    Tu nous donne le code principale mais des fonctions comme GetDataAsStreamFromVariant reste obscure !

    Tu devrais repartir d'un cas simple !

    ParamValue est un variant, donc cela passe par une chaine, mais ta chaine de ton variant est ANSI ou UniCode ? Rien que de voir ça retire tout l'intérêt du Stream

    Comment appeles-tu InternalAddParam ? c'est dans AddParamToQuery ?
    Quelque chose t'empeche d'utiliser un TStream tout du long ?
    Comment est rempli VarValue ?
    VarValue était un array of byte, vérifie avec VarType() si c'est bien un array !
    Pourquoi ne pas avoir fait "Stream.Write(Bytes, Len)"

    c'est dommage de faire partiellement une requête paramètrée (série de + + +)!
    BDE = Paradox ??? tu ne peux pas insérer un BLOB en SQL, c'est dommage
    Car en Oracle, MySQL, PostGreSQL, avec un encodage Base64 tu peux insérer les BLOB directement dans un INSERT

    Pour le #0, logique en UniCode, il faut deux #0 pour indiquer la fin de chaine !
    D'où l'impression d'un meilleur fonctionnement !
    Ne jamais utiliser AsValue pour un Blobl ! Jamais !
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  5. #5
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par ShaiLeTroll Voir le message
    Bon, tu as des fonctions et types persos Dia... cela n'aide pas à comprendre !
    Tu nous donne le code principale mais des fonctions comme GetDataAsStreamFromVariant reste obscure !

    Tu devrais repartir d'un cas simple !

    ParamValue est un variant, donc cela passe par une chaine, mais ta chaine de ton variant est ANSI ou UniCode ? Rien que de voir ça retire tout l'intérêt du Stream

    Comment appeles-tu InternalAddParam ? c'est dans AddParamToQuery ?
    Quelque chose t'empeche d'utiliser un TStream tout du long ?
    Comment est rempli VarValue ?
    VarValue était un array of byte, vérifie avec VarType() si c'est bien un array !
    Pourquoi ne pas avoir fait "Stream.Write(Bytes, Len)"

    c'est dommage de faire partiellement une requête paramètrée (série de + + +)!
    BDE = Paradox ??? tu ne peux pas insérer un BLOB en SQL, c'est dommage
    Car en Oracle, MySQL, PostGreSQL, avec un encodage Base64 tu peux insérer les BLOB directement dans un INSERT

    Pour le #0, logique en UniCode, il faut deux #0 pour indiquer la fin de chaine !
    D'où l'impression d'un meilleur fonctionnement !
    Ne jamais utiliser AsValue pour un Blobl ! Jamais !
    Merci de ta réponse ShaiLeTroll.

    Effectivement il manque le code de la fonction "AddParamToQuery" que voici :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function AddParamToQuery( AQuery: TQuery; const AFieldName: string;
                              const AParamValue: Variant;
                              AFieldType: TDiaDBFieldType; AReturnBind: TReturnBind;
                              AAutoClearParams: Boolean=False ): string;
    begin
       InternalAddParam( AQuery, AFieldType, Dts_GetBindField( AFieldName ),
                         AParamValue, AAutoClearParams );
     
       Result:= Dts_BindSign( AFieldName, AReturnBind );
    et 'Dts_BindSign' retourne le nom du paramètre précédé de ':'...

    Pour le reste j'ai en principe tout expliqué dans mes précédents posts :
    'TDiaDBFieldType' est une "pseudo-translation" TFieldType (traduit par la fonction 'DiaDBFieldTypeToFieldType') et 'ddbftBinary' est ainsi traduit en 'ftBlob'. C'est juste que le type 'TDiaDBFieldType' différencie certains type que ne fait pas 'TFieldType'...

    De même voici comment est affecté 'VarValue' :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
          CompressDataAsVariantFromStream( VarValue, Stream, DummyInt );
    Cette ligne précède l'appel à 'AQuery.SQL.Text:='... comme tu vois c'est encore une procédure, et je ne peux pas tout mettre car cela va commencer à faire un paquet de code et selon moi ce n'est pas pertinent.
    Je pense que l'important est de savoir que 'VarValue' contient un array of byte (j'ai évidemment vérifié le type via vartype = $2011 si mes souvenirs sont bons, enfin array of byte ça c'est sûr). Donc 'ParamValue' est également de ce type lors de l'appel et même, le paramètre retourné :
    sa propriété 'Value' est un variant de même type (array of byte).

    A partir de là je me dis que le paramètre est correctement défini et que ma requête devrait donc s'exécuter normalement non ?


    Petites précisions :

    Nos champs blobs en base de données ne contiennent que des données binaires (car contenu compressé), et c'est pourquoi nous utilisons le type qui semble le plus logique et "safe" : "array of byte". Notre application est multi-SGBD (Oracle, SQLServeur, MySQL mais aussi Access) et cela nous "interdit" (à moins de multiplier le code) les spécificités liées à l'utilisation de type "non générique" comme les champs BLOB en base de données. En l'occurrence j'ai effectué mon test sur une base de données de type Access.

    Il est vrai que c'est dommage de n'avoir qu'une requête semi-paramétré, mais dans le cas présent nous n'avons réellement pas besoin de performance, cette requête étant exécutée rarement. La paramètre découle donc simplement du choix de notre implémentation pour insérer des champs binaires en base de données.

  6. #6
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Personne n'a été confronté à ce problème ? Cela m'étonne, et je ne vois pas comment trouver de solution simple et rapide.

    ...Je me vois déjà tout convertir en DBX, le truc qui va me prendre plus d'un mois... :-(... c'est quand même pas possible que ce soit un "bug" introduit dans Delphi 2009 (car je soupçonne que cela ait un rapport avec l'Unicode, bien qu'en principe je n'utilise pas le type "string" pour passer mes données binaires)

  7. #7
    Expert éminent sénior
    Avatar de Cl@udius
    Homme Profil pro
    Développeur Web
    Inscrit en
    Février 2006
    Messages
    4 878
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Février 2006
    Messages : 4 878
    Points : 10 008
    Points
    10 008
    Par défaut
    Salut
    Citation Envoyé par ZZZzzz2 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //...
       if ( AFieldType = ddbftBinary ) then begin
          MyStream:= TMemoryStream.Create;
          try
             GetDataAsStreamFromVariant( ParamValue, MyStream );
             Result.LoadFromStream( MyStream, ftBlob );
          finally
             MyStream.Free;
          end;
       end else Result.Value:= ParamValue;
    end;
    Est-ce qu'il ne manquerait pas un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    MyStream.Seek(0, soFromBeginning);
    juste avant le LoadFromStream ?

    Question à tout hasard, sans savoir si celle-ci est pertinente.

    @+ Claudius.

  8. #8
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 455
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 455
    Points : 24 867
    Points
    24 867
    Par défaut
    Tu as oublié de founir
    GetDataAsStreamFromVariant
    CompressDataAsVariantFromStream

    tu nous sors des fonctions que tu as développé !
    Et plus on avance, et plus tu nous en donne de nouvelles !

    Et c'est juste le passage Variant (2011 array of WordBool) vers Stream qui est interressant, imagine qu'en 2009, CodeGead a changé l'implémentation des structures TVarData et TVarRec (qui ne sont pas compatible évidemment )

    le mieux est de vérifier proprement
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if VarIsType(V1, varArray or varByte) then
    Sinon, lorsque l'on n'utilise que des Stream pour manipuler les Blobs sans passer par des variants, on a pas ces problèmes, c'est juste une uzinagaz ton truc !

    Perso, j'utilisais aussi une compression mais toujours en Stream !
    Le MemoryStream n'étant qu'une zone mémoire, c'est très polyvalent, il est vrai qu'un array of byte est assez proche !


    @Cl@udius, ben oui, c'est pour cela que je voulais le code de GetDataAsStreamFromVariant, j'ai pensé au bon vieux seek 0 mais son code fonctionne en D7 mais plus en 2009, qui laisse supposer un problème de taille du char (et par conséquent taille de buffer)

    Perso, j'ai souvent une option AutoRewind (défaut à true) dans mes Fonctions d'écriture dans les Stream !
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  9. #9
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par Cl@udius Voir le message
    Salut


    Est-ce qu'il ne manquerait pas un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    MyStream.Seek(0, soFromBeginning);
    juste avant le LoadFromStream ?

    Question à tout hasard, sans savoir si celle-ci est pertinente.

    @+ Claudius.
    [Edit] je n'avais pas bien lu ton post, en fait un 'LoadFromStream' remet à 0 le stream passé en argument, donc c'est inutile :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    procedure TMemoryStream.LoadFromStream(Stream: TStream);
    var
      Count: Longint;
    begin
      Stream.Position := 0;
      Count := Stream.Size;
      SetSize(Count);
      if Count <> 0 then Stream.ReadBuffer(FMemory^, Count);
    end;

  10. #10
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 455
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 455
    Points : 24 867
    Points
    24 867
    Par défaut
    Tout dépend du code de GetDataAsStreamFromVariant et du mystérieux CompressDataAsVariantFromStream
    Cette fonction étant entre le Create et le LoadFromFile, comme on ne connait pas son contenu, la remarque de Cl@udius est tout à fait pertinente et ta réponse nous amène à deux possibilités :
    - Tu connais le code et donc tu sais qu'il n'y a pas de problème à ce sujet
    - Tu as trop la tête dans le guidon pour remarquer le détail qui tue !

    Si tu utilises dedans un Write pour écrire le Variant, la Position dans le Stream change après le Write !
    D'où la necessité de faire un Rewind !
    Mais comme ton code fonctionnait en D7, cela doit déjà le gérer !

    Tu poses des Questions ZZzzzz mais tu ne réponds pas aux notre, tout ne nous donne pas les élements utiles et tu balayes nos remarques d'un revert Dédaigneux ! C'est un peu lassant !
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  11. #11
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par ShaiLeTroll Voir le message
    Tu as oublié de founir
    GetDataAsStreamFromVariant
    CompressDataAsVariantFromStream

    tu nous sors des fonctions que tu as développé !
    Et plus on avance, et plus tu nous en donne de nouvelles !

    Et c'est juste le passage Variant (2011 array of WordBool) vers Stream qui est interressant, imagine qu'en 2009, CodeGead a changé l'implémentation des structures TVarData et TVarRec (qui ne sont pas compatible évidemment )

    le mieux est de vérifier proprement
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if VarIsType(V1, varArray or varByte) then
    Sinon, lorsque l'on n'utilise que des Stream pour manipuler les Blobs sans passer par des variants, on a pas ces problèmes, c'est juste une uzinagaz ton truc !

    Perso, j'utilisais aussi une compression mais toujours en Stream !
    Le MemoryStream n'étant qu'une zone mémoire, c'est très polyvalent, il est vrai qu'un array of byte est assez proche !


    @Cl@udius, ben oui, c'est pour cela que je voulais le code de GetDataAsStreamFromVariant, j'ai pensé au bon vieux seek 0 mais son code fonctionne en D7 mais plus en 2009, qui laisse supposer un problème de taille du char (et par conséquent taille de buffer)

    Perso, j'ai souvent une option AutoRewind (défaut à true) dans mes Fonctions d'écriture dans les Stream !

    Voici les fonctions en question :


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    procedure GetDataAsStreamFromVariant(const Data: variant; AStream: TStream);
    var
       p: Pointer;
       len: Integer;
    begin
       Assert( AStream <> nil );
     
       // len:= VarArrayHighBound(Data, 1);
       len:= Succ( VarArrayHighBound(Data, 1) );
     
       // on copie les données dans le tableau
       p:= VarArrayLock(Data);
       try
          AStream.Position:= 0;
          AStream.WriteBuffer(p^, len);
       finally
          VarArrayUnlock(Data);
       end;
    end;

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function CompressDataAsVariantFromStream(var Data: variant; AStream: TStream;
                                             out CompressedDataSize: Int64;
                                             Verbose: Boolean=False): boolean;
    var
       CompressedFile: TMemoryStream;
    begin
       CompressedFile:= TMemoryStream.Create;
       try
          // Comme la fonction retourne 'False' en cas d'échec il ne
          // faut pas qu'on puisse générer des exceptions
          try
             Compress( AStream, CompressedFile );
             CompressedDataSize:= CompressedFile.Size;
             Data:= GetDataAsVariantFromStream( CompressedFile );
             Result:= True;
          except
             On E: EZlibError do begin
                Result:= False;
                if ( Verbose ) then MessageDlgFront( 'Exception "'+E.ClassName+'" lors de l''appel "U_VarTools.CompressDataAsVariantFromStream".'+sLineBreak+
                                                     'Message d''erreur = "'+( E.Message )+'"', mtError )
             end;
          end;
       finally
          CompressedFile.Free;
       end;
    end;

    et comme tu vas me le demander voici une dernière :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
     
    function GetDataAsVariantFromStream(AStream: TStream): variant;
    var
       p: Pointer;
       len: integer;
    begin
       Assert( AStream <> nil );
     
       // par défaut on initialise toujours le tableau avec un seul élément = 0
       result:= GetEmptyVarArray(VarByte);
     
       len:= AStream.Size;
       // Il faut faire le test '>=' afin de gérer le cas des tableaux sans élément
       if (len >= 0) then begin
          VarArrayRedim(Result, Pred( len ));
     
          // on copie les données dans le tableau
          p:= VarArrayLock(Result);
          try
             AStream.Position:= 0;
             AStream.ReadBuffer(p^, len);
          finally
             VarArrayUnlock(Result);
          end;
       end;
    end;



    Enfin vous vous attardez sur la gestion des TStream qui rend le tout confus, je l'avoue, et c'est la raison pour laquelle je n'avais pas mis le code source car de mon point de vue le problème ne provient pas de là puisque le contenu du Variant résultant est correcte.

    Preuve en est, j'ai modifié ma fonction de départ comme suit :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    function InternalAddParam( AQuery: TQuery; AFieldType: TDiaDBFieldType; const ParamName: string;
                               ParamValue: variant; AAutoClearParams: Boolean=False ): TParam;
    begin
       Assert( AQuery <> nil );
     
       if ( AAutoClearParams ) then begin
          AQuery.Params.Clear;
          Result:= nil;
     
       end else Result:= AQuery.Params.FindParam( ParamName );
     
       if ( Result = nil ) then
          Result:= AQuery.Params.CreateParam( DiaDBFieldTypeToFieldType( AFieldType ), ParamName, ptInput );
     
       Assert( Result <> nil );
     
       // Depuis Delphi 2009 et le support de l'unicode il n'y a plus besoin de
       // passer par 'TParam.LoadFromStream' pour que celui-ci contienne bien
       // le contenu du tableau d'octets via 'AsBlob'. Je me contente d'une
       // affectation directe :
       Result.Value:= ParamValue;
    end;

    Et bien sûr le résultat est identique ! Désolé, j'aurai peut-être du commencer par là (on aurait gagné du temps) mais je ne voulais pas modifié mon ancienne implémentation trop rapidement...

    Désolé si je paraît dédaigneux dans mes réponses, en réalité je réponds vite, et je peux manquer (mais c'est involontaire) de courtoisie...

  12. #12
    Expert éminent sénior
    Avatar de Cl@udius
    Homme Profil pro
    Développeur Web
    Inscrit en
    Février 2006
    Messages
    4 878
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Février 2006
    Messages : 4 878
    Points : 10 008
    Points
    10 008
    Par défaut
    Salut

    Je ne vois pas de Seek pour se repositionner en début de stream, dans les procédures que tu présentes.

    Teste tout de même en ajoutant un Seek(0, 0) avant la lecture du flux.

    @+ Claudius

  13. #13
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par Cl@udius Voir le message
    Salut

    Je ne vois pas de Seek pour se repositionner en début de stream, dans les procédures que tu présentes.

    Teste tout de même en ajoutant un Seek(0, 0) avant la lecture du flux.

    @+ Claudius
    'Position:= 0' revient exactement au même, mais comme je le dis, le problème ne vient pas des TStream... voir mon post précédent.

  14. #14
    Expert éminent sénior
    Avatar de Cl@udius
    Homme Profil pro
    Développeur Web
    Inscrit en
    Février 2006
    Messages
    4 878
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Février 2006
    Messages : 4 878
    Points : 10 008
    Points
    10 008
    Par défaut
    Ah OK, je viens de voir ton [Edit].

  15. #15
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 455
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 455
    Points : 24 867
    Points
    24 867
    Par défaut
    On se comprend mieux
    En D7, tu faisais Stream -> Compress -> Variant -> Stream -> LoadFromStream
    En D2009, tu fais Stream -> Compress -> Variant -> AsValue

    Tu as simplifié, j'imagine que tu as testé déjà un tas de variante !
    OK, on est d'accord maintenant, tu nous proposes un code assez complexe, faut tous les éléments pour que l'on puisse comprendre et écarter les mauvaises idées

    Question con ?
    Pourquoi ne pas avoir directement utiliser
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    VarArrayCreate([0, len -1], VarByte)
    Tu vérifie le contenu de tes blobs de quelle manière ?
    Je ne suis pas un expert ACCESS !
    Je connais mieux MySQL et MySQL Query Browser (idéal pour manipuler les Blob)
    As-tu testé un petit binaire genre 10 octets ?
    Et quel est le résultat ?
    La connexion BDE <-> ACCESS utilise quel CharSet ?
    Le Type dans ACCESS c'est quoi ? Memo ? BLOB (OLE Object) ?
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  16. #16
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par ShaiLeTroll Voir le message
    On se comprend mieux
    En D7, tu faisais Stream -> Compress -> Variant -> Stream -> LoadFromStream
    En D2009, tu fais Stream -> Compress -> Variant -> AsValue

    Tu as simplifié, j'imagine que tu as testé déjà un tas de variante !
    OK, on est d'accord maintenant, tu nous proposes un code assez complexe, faut tous les éléments pour que l'on puisse comprendre et écarter les mauvaises idées

    Question con ?
    Pourquoi ne pas avoir directement utiliser
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    VarArrayCreate([0, len -1], VarByte)
    Tu vérifie le contenu de tes blobs de quelle manière ?
    Je ne suis pas un expert ACCESS !
    Je connais mieux MySQL et MySQL Query Browser (idéal pour manipuler les Blob)
    As-tu testé un petit binaire genre 10 octets ?
    Et quel est le résultat ?
    La connexion BDE <-> ACCESS utilise quel CharSet ?
    Le Type dans ACCESS c'est quoi ? Memo ? BLOB (OLE Object) ?
    Dans mon premier post tout était déjà expliqué en réalité :

    "Pour information le contenu variant passé en argument est de type 'array of byte', si je passe par un stream lors de l'utilisation d'un champ binaire (ici 'ddbftBinary' est équivalent à 'ftblob') c'est parce-que sans cela sous Delphi7 le contenu du paramètre n'était pas bien affecté en utilisant directement 'Result.Value:= ParamValue;' (le variant obtenu était de type 'string' et les données situés après le premier #0 étaient tronquées). Ce qui ne semble d'ailleurs plus être le cas depuis Delphi 2009, mais j'ai laissé l'implémentation telle quelle pour le moment..."

    J'ai donc depuis modifié l'implémentation pour que cela vous paraisse plus clair, mais la chose importante c'est de revenir à la source :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
          AQuery.SQL.Text:= 'Insert Into LICENSE ( ' +
                            Cst_Bdd_LicenseCreationDate + ', ' +
                            Cst_Bdd_LicenseUserName + ', ' +
                            Cst_Bdd_LicenseData + ' ) Values ( ' +
                            Dts_FormatDateTime( FInsertDate ) + ', ' +
                            Dts_FormatString( AUserName ) + ', ' +
                            AddParamToQuery( AQuery, Cst_Bdd_LicenseData, VarValue,
                                             ddbftBinary, rbValue ) + ' )';
          // Pourquoi cela ne fonctionne-t-il pas ?
          // Le contenu du paramètre semble OK, la requête aussi, mais
          // son exécution me retourne un champ contenant que des '€€€€' sous
          // Delphi 2009 ???
          // Le même code fonctionne parfaitement sous Delphi 7...
          AQuery.ExecSQL;

    On sait que 'VarValue' contient un array of byte, peu importe la manière dont il a récupéré ce contenu à mon avis. On sait maintenant que je copie ce contenu dans la valeur "Variant" (de type array of byte) d'un paramètre de type 'blob' que je créé et qu'à l'exécution cela ne fonctionne pas.
    Le contenu du paramètre est correcte (vérifié par le débogueur). J'ai déjà essayé pas mal de choses, dont le code suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    MyParam:= Query.Params.CreateParam( ftBlob, 'TEST', ptInput );
    MyVariant:= VarArrayCreate( [0, 10 -1], VarByte );
    MyVariant[0]:= 65;
    MyVariant[1]:= 66;
    MyVariant[2]:= 67;
    MyVariant[3]:= 68;
    MyVariant[4]:= 69;
    MyVariant[5]:= 70;
    MyVariant[6]:= 71;
    MyVariant[7]:= 72;
    MyVariant[8]:= 73;
    MyVariant[9]:= 75;
    MyParam.Value:= MyVariant;
     
    Query.SQL.Text:= 'Insert Into LICENSE ( CREATIONDATE, USERNAME, LICENSEDATA ) Values ( ' +
                            Dts_FormatDateTime( Now ) + ', ' +
                            Dts_FormatString( 'Moi' ) + ', :TEST )';
    Query.ExecSQL;
    J'obtiens un résultat = #63#63#63#63 (4 caractères) (à la place de #65#66#67...), je ne sais plus quoi faire pour corriger cela...
    Passer à DBX me prendra au moins un mois, et si ça se trouve j'aurai plein d'autres problèmes (vu le nombre de problèmes que j'ai déjà eu avec le BDE mais pour lesquels nous avons toujours trouvé une solution).


    Pour information sous Access j'utilise le type de champ "Objet OLE" pour stocker du binaire, cela a toujours bien fonctionné, sous Delphi 7.
    Par ailleurs je ne sais pas où trouver l'information concernant le CharSet utilisé. Mais le problème n'est pas lié au driver Access, puisque j'ai essayé un autre driver Access, mais également sur une base de données SQL Server ou Oracle et j'ai exactement la même erreur...

  17. #17
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 455
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 455
    Points : 24 867
    Points
    24 867
    Par défaut
    As-tu essayé ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    MyVariant:= VarArrayCreate( [0, 10 -1], VarByte );
    MyVariant[0]:= 65;
    MyVariant[1]:= 66;
    MyVariant[2]:= 67;
    MyVariant[3]:= 68;
    MyVariant[4]:= 69;
    MyVariant[5]:= 70;
    MyVariant[6]:= 71;
    MyVariant[7]:= 72;
    MyVariant[8]:= 73;
    MyVariant[9]:= 75;
     
    Query.SQL.Text:= 'Insert Into LICENSE ( CREATIONDATE, USERNAME, LICENSEDATA ) Values ( ' +
                            Dts_FormatDateTime( Now ) + ', ' +
                            Dts_FormatString( 'Moi' ) + ', :TEST )';
    MyParam:= Query.ParamByName('TEST').Value:= MyVariant;
     
    Query.ExecSQL;
    Il faut penser aussi à ParamCheck (dans ton cas, faudrait qu'il soit à false, et faudrait un clear par contre pour le code ci-dessus, il le faut à true)

    En BDE, on peut avoir plusieurs paramètres qui s'appellent TEST !
    l'utilisation de ParamByName va modifier tous les paramètres nommés TEST
    l'utilisation d'une variable TParam ne modifie qu'un paramètre !

    En ADO, on peut avoir plusieurs paramètres qui s'appellent TEST !
    l'utilisation de ParamByName va modifier le premier paramètre nommé TEST, et laissera une valeur nulle dans les autres

    ORACLE même SQL lancé via BDE ou ODA = DataSet différent
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  18. #18
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par ShaiLeTroll Voir le message
    As-tu essayé ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    MyVariant:= VarArrayCreate( [0, 10 -1], VarByte );
    MyVariant[0]:= 65;
    MyVariant[1]:= 66;
    MyVariant[2]:= 67;
    MyVariant[3]:= 68;
    MyVariant[4]:= 69;
    MyVariant[5]:= 70;
    MyVariant[6]:= 71;
    MyVariant[7]:= 72;
    MyVariant[8]:= 73;
    MyVariant[9]:= 75;
     
    Query.SQL.Text:= 'Insert Into LICENSE ( CREATIONDATE, USERNAME, LICENSEDATA ) Values ( ' +
                            Dts_FormatDateTime( Now ) + ', ' +
                            Dts_FormatString( 'Moi' ) + ', :TEST )';
    MyParam:= Query.ParamByName('TEST').Value:= MyVariant;
     
    Query.ExecSQL;
    Il faut penser aussi à ParamCheck (dans ton cas, faudrait qu'il soit à false, et faudrait un clear par contre pour le code ci-dessus, il le faut à true)

    En BDE, on peut avoir plusieurs paramètres qui s'appellent TEST !
    l'utilisation de ParamByName va modifier tous les paramètres nommés TEST
    l'utilisation d'une variable TParam ne modifie qu'un paramètre !

    En ADO, on peut avoir plusieurs paramètres qui s'appellent TEST !
    l'utilisation de ParamByName va modifier le premier paramètre nommé TEST, et laissera une valeur nulle dans les autres

    ORACLE même SQL lancé via BDE ou ODA = DataSet différent

    J'ai essayé le code suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
    Query.Params.Clear;
    MyVariant:= VarArrayCreate( [0, 10 -1], VarByte );
    MyVariant[0]:= 65;
    MyVariant[1]:= 66;
    MyVariant[2]:= 67;
    MyVariant[3]:= 68;
    MyVariant[4]:= 69;
    MyVariant[5]:= 70;
    MyVariant[6]:= 71;
    MyVariant[7]:= 72;
    MyVariant[8]:= 73;
    MyVariant[9]:= 75;
     
    Query.SQL.Text:= 'Insert Into LICENSE ( CREATIONDATE, USERNAME, LICENSEDATA ) Values ( ' +
                            Dts_FormatDateTime( Now ) + ', ' +
                            Dts_FormatString( 'Moi' ) + ', :TEST )';
    MyParam:= Query.ParamByName('TEST');
    Assert( MyParam <> nil );
    MyParam.DataType:= ftBlob;
    MyParam.Value:= MyVariant;
     
    Query.ExecSQL;
    j'ai du préciser le type du paramètre sinon cela me déclenchait une erreur "Type de paramètre non défini" à l'exécution de la requête...
    Le résultat est le même ...

  19. #19
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 455
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 455
    Points : 24 867
    Points
    24 867
    Par défaut
    Pour vérifier, tu utilises des outils externes ou tu lances une requête dans ton programme !

    C'est effectivement fort pénible ton problème !
    Essaye avec une version XE Trial, il trainait des bugs en 2009 !

    Idée, essaye qu'avec des zéros dans MyVariant !
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  20. #20
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par ShaiLeTroll Voir le message
    Pour vérifier, tu utilises des outils externes ou tu lances une requête dans ton programme !

    C'est effectivement fort pénible ton problème !
    Essaye avec une version XE Trial, il trainait des bugs en 2009 !

    Idée, essaye qu'avec des zéros dans MyVariant !
    Je lance les requêtes avec mon programme, enfin j'ai essayé différents modules qui se connectent d'une façon légèrement différente sur la base de données mais le résultat est le même.
    En fait dès que je passe par le BDE je ne peux plus insérer de valeurs binaires. C'est extrêmement gênant car pour le moment le passage à Delphi 2009 est bloqué à cause de cela, et donc je suis obligé tant que cela n'est pas résolu de fusionner les modifications faites sur la version Delphi 7 dans Delphi 2009, ce qui merdique au possible car l'air de rien il y a beaucoup de modifications chaque semaine...

    Pour information si j'essaie de n'insérer que des 0 alors mon champ en base de données reste vide, si j'essaie d'insérer des 1 je récupère 61...
    C'est très probablement lié à la nouvelle gestion Unicode de Delphi 2009 puisque je pense que les 0 sont considérés comme la fin de la chaîne de caractères et ne sont donc pas insérés en base de données. Cela signifie que le contenu de mon paramètre, bien qu'étant de type 'blob' et que le variant interne soit de type 'array of Byte' est considéré en réalité comme une chaine de caractères Unicode (2 octets) , lesquels sont retranscrits en caractères Ansi (1 octet) dans le champ. Ceci pourrait expliquer cela... Je pense donc que c'est (sale) bug, et je ne suis pas sûr qu'en déboguant la VCL je trouve l'erreur (car si celle-ci intervient au niveau du BDE, il n'y a pas le code source).


    Cela dis je ne peux pas croire qu'il n'existe pas de solution sous Delphi 2009...

Discussions similaires

  1. Réponses: 2
    Dernier message: 04/10/2014, 18h02
  2. [phpMyAdmin] Je ne peux plus insérer des données après avoir renommé une table!
    Par yvessavoie dans le forum EDI, CMS, Outils, Scripts et API
    Réponses: 1
    Dernier message: 14/02/2014, 16h22
  3. Delphi 2009 plus lent que Delphi 5
    Par portu dans le forum EDI
    Réponses: 0
    Dernier message: 30/09/2009, 10h50
  4. Réponses: 0
    Dernier message: 23/04/2009, 09h38
  5. [IB71] Je ne peux plus supprimer mes foreign key...
    Par BoeufBrocoli dans le forum InterBase
    Réponses: 3
    Dernier message: 19/09/2003, 14h39

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo