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

Langage Delphi Discussion :

Récupérer le type enregistré dans un objet


Sujet :

Langage Delphi

  1. #1
    Membre habitué

    Homme Profil pro
    Développeur multimédia
    Inscrit en
    Février 2013
    Messages
    148
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Landes (Aquitaine)

    Informations professionnelles :
    Activité : Développeur multimédia
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Février 2013
    Messages : 148
    Points : 199
    Points
    199
    Par défaut Récupérer le type enregistré dans un objet
    Bonjour,

    J'ai fait une méthode pour mettre à jour les enregistrements d'une table
    J'ai en parramètre de ma méthode un TDictionnary, dans lequel en index figure les champs de ta table, et en valeur les champs à enregistrer.
    Les champs sont du type integer ou varchar.
    Mon TDIctionnary est du type TDictionnary<string, TObject>
    Est t-il possible de connaître quel type est enregistré dans le TObject, si c'est un entier ou un string, afin de créer correctement ma requete ?
    Je pense que ce que je demande n'est pas cohérent, mais je teste quand 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
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
     
    procedure TServerMethods2.ModifClient(
      const pdArgVal: TDictionary<string, TObject>);
      var
         dbxTrans : TDBXTransaction;
         Key: String;
         sep: String;
    begin
           // Démarrage de la transaction
         dbxTrans := TEST.BeginTransaction;
         sep := '';
         With TSQLQuery.Create(nil) do begin
              SQLConnection := TEST;
              SQL.Add('UPDATE CLIENTS SET');
              for Key in pdArgVal.Keys do
                  begin
                       // pdArgVal[Key] est une chaine
                       if(pdArgVal[Key]...)
                           SQL.Add(sep+Key+'='''+pdArgVal[Key]+'');
                       // pdArgVal[Key] est un entier
                       else
                           SQL.Add(sep+Key+'='+pdArgVal[Key]);
                       sep := ',';
                  end;
         end;
     
    end;

  2. #2
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 612
    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 612
    Points : 25 303
    Points
    25 303
    Par défaut
    is
    ClassType
    ClassName
    InheritsFrom
    ...

    TObject pour un entier ? Bizarre !
    Si tu fais du cast sauvage, ça devient plus compliqué, peut-être du Variant ?
    ou alors voir si les nouvelles RTTI peuvent aider !

  3. #3
    Membre habitué

    Homme Profil pro
    Développeur multimédia
    Inscrit en
    Février 2013
    Messages
    148
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Landes (Aquitaine)

    Informations professionnelles :
    Activité : Développeur multimédia
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Février 2013
    Messages : 148
    Points : 199
    Points
    199
    Par défaut
    TObject car il faut que je stocke ou un entier ou une chaine

  4. #4
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 612
    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 612
    Points : 25 303
    Points
    25 303
    Par défaut
    Mais comme un entier ou une chaine ne descende pas du TObject, je ne vois pas bien sauf si tu as encapsulé cela comme ceci

    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
    type
      TObjectString = class
      private
        FValue: string;
      public
        constructor Create(const AValue: string = '');
        property Value: string read FValue write FValue;
      end;
     
      TObjectInteger = class
      private
        FValue: Integer;
      public
        constructor Create(const AValue: Integer= 0);
        property Value: Integer read FValue write FValue;
      end;
    Au final, autant utiliser un Variant qui fourni déjà cela : VarIsStr, VarIsNumeric...

    Enfin comme tout cela est pour générer du SQL, il existe aussi ParamByName, justement un paramètre c'est un Variant fortement typé

    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
    type
      TArg = class
      private
        FValue: Variant;
        function GetDataType(): TFieldType;
      public
        constructor Create(const AValue: Variant);
        property Value: Variant read FValue write FValue;
        property DataType: TFieldType read GetDataType;
      end;
     
    function TArg::GetDataType(): TFieldType;
    begin
      Result := ftString;
      if VarIsOrdinal(FValue) then
        Result := ftInteger;
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    procedure TServerMethods2.ModifClient(const pdArgVal: TDictionary<string, TObject>);
    var
      dbxTrans : TDBXTransaction;
      Key: String;
      sep: String;
    begin
      // Démarrage de la transaction
      dbxTrans := TEST.BeginTransaction;
      sep := '';
      With TSQLQuery.Create(nil) do 
      begin
        SQLConnection := TEST;
        SQL.BeginUpdate(); // Si l'on fait beaucoup de SQL.Add, cela accélère le traitement
        try
          SQL.Add('UPDATE CLIENTS SET');
          for Key in pdArgVal.Keys do
          begin
            if pdArgVal[Key] is TArg then
            begin
              SQL.Add(Format('%1:s%0:s = :param%0:s', [Key, sep]); // Pas de prise de tête avec '''', on laisse ParamByName le faire à notre place !
              sep := ',';
            end;
          end;
        finally
          SQL.EndUpdate(); // Indique que l'on a fini les SQL.Add et par conséquence provoque le ParamCheck !
        end;
     
        for Key in pdArgVal.Keys do
        begin
          if pdArgVal[Key] is TArg then
          begin
            with ParamByName(Format('param%0:s'), [Key])) do 
            begin
              Value = (pdArgVal[Key] as TArg)->Value; // En DBExpress + Sybase 10 ASA, ça fonctionne parfaitement, je l'utilise massivement couplé aux RTTI via GetPropValue
              // DataType = (pdArgVal[Key] as TArg)->DataType; // Selon Provider et Driver, Value n'est pas assez typé !
            end;
          end;
        end;
     
        ExecSQL(); // Faudrait pas l'oublier lui !
     
      end; 
      TEST.CommitFreeAndNil(dbxTrans); // penser au RollBack si exception
    end;

  5. #5
    Membre habitué

    Homme Profil pro
    Développeur multimédia
    Inscrit en
    Février 2013
    Messages
    148
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Landes (Aquitaine)

    Informations professionnelles :
    Activité : Développeur multimédia
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Février 2013
    Messages : 148
    Points : 199
    Points
    199
    Par défaut
    Merci, désolé je débute en Delphi je ne connaissais pas les Variants !!
    C'est nickel !!!

    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
    29
    30
    31
    32
    33
    34
    35
    36
     
    procedure TServerMethods2.ModifClient(const pdArgVal: TDictionary<string, Variant>);
    const
         TslallowedFields = TArray<string>.Create('Nom, Prenom, Date_Naissance, Adresse, Code_Postal');
      var
         dbxTrans : TDBXTransaction;
         Key: String;
         sep: String;
    begin
         // Démarrage de la transaction
         dbxTrans := TEST.BeginTransaction;
         sep := '';
         With TSQLQuery.Create(nil) do begin
              SQLConnection := TEST;
              SQL.Add('UPDATE CLIENTS SET');
              for Key in pdArgVal.Keys do
                  begin
                       if MatchStr(Key, TslallowedFields) then
                          begin;
                                // La valeur est une chaine
                               if(VarIsStr(pdArgVal[Key]) = true)then
                                   begin;
                                         SQL.Add(sep+Key+'='''+pdArgVal[Key]+'');
                                   end
                               //  La valeur est un entier
                               else
                                   begin;
                                         SQL.Add(sep+Key+'='+pdArgVal[Key]);
                                   end;
     
                               sep := ',';
                          end;
                  end;
         end;
         pdArgVal.Free;
    end;

  6. #6
    Membre habitué

    Homme Profil pro
    Développeur multimédia
    Inscrit en
    Février 2013
    Messages
    148
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Landes (Aquitaine)

    Informations professionnelles :
    Activité : Développeur multimédia
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Février 2013
    Messages : 148
    Points : 199
    Points
    199
    Par défaut
    J'avais pas vu ton code ShaiLeTroll

    Très très instructif, merci beaucoup !!

  7. #7
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 457
    Points
    28 457
    Par défaut
    je trouve qu'il y a un défaut de logique dans cette approche, ce n'est pas la valeur qui est typée, c'est le champ, je peux très bien mettre l'entier 123 dans un champ texte, mais avec des quotes. Et je peux stocker la chaîne "123" dans un champ numérique...sans les quotes.

  8. #8
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 612
    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 612
    Points : 25 303
    Points
    25 303
    Par défaut
    Citation Envoyé par Paul TOTH Voir le message
    je trouve qu'il y a un défaut de logique dans cette approche, ce n'est pas la valeur qui est typée, c'est le champ, .
    Le Champ est typé
    Le Paramètre d'un SQL doit être typé
    La Valeur c'est déjà plus confus, personnellement ayant des objets métiers, c'est fortement typé et en phase avec la DB

    Cet amalgame de typer fortement les valeurs dans son code orienté DB me semble un grand classique que l'on hérite du TDataSet avec TIntegerField, TStringField ...

    Citation Envoyé par Paul TOTH Voir le message
    je peux très bien mettre l'entier 123 dans un champ texte, mais avec des quotes. Et je peux stocker la chaîne "123" dans un champ numérique...sans les quotes.
    Peut-être que son SGBD ne fait pas de conversion automatique des types, et qu'il est obligé d'écrire un SQL fortement typé conforme à son schéma !

    Par contre Paul TOTH, ton histoire de avec ou sans quote, n'est-ce pas justement les quote qui donne le type en SQL ?
    Ce n'est pas très clair comme propos !

    Dans l'histoire, je ne vois pas ce qui te choques, que soit avec FieldByName ou ParamByName, j'utilise systématiquement AsString, AsInteger, AsDate... selon mon schéma, j'évite les mélanges de type !

  9. #9
    Membre habitué

    Homme Profil pro
    Développeur multimédia
    Inscrit en
    Février 2013
    Messages
    148
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Landes (Aquitaine)

    Informations professionnelles :
    Activité : Développeur multimédia
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Février 2013
    Messages : 148
    Points : 199
    Points
    199
    Par défaut
    Tu as entièrement raison, mais est ce que je peux identifier autrement le type de champs qu'en envoyant un type de paramètre ?

    Voilà ma méthode actuelle :

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
     
    procedure TForm4.updateClients(pdKeyValueDatas: TDictionary<string, Variant>; piId: string);
     
      var
         dbxTrans : TDBXTransaction;
         Key: String;
         sep: String;
    begin
         // Démarrage de la transaction
         dbxTrans := SQLConnection1.BeginTransaction;
         sep := '';
         With TSQLQuery.Create(nil) do begin
              SQLConnection := SQLConnection1;
              SQL.Add('UPDATE CLIENTS_2013 SET');
     
              // On boucle sur les données envoyées
              for Key in pdKeyValueDatas.Keys do
                  begin
                       // La valeur est une chaine
                      if VarIsStr(pdKeyValueDatas[Key]) then
                      begin
                         SQL.Add(sep+Key+' = '''+pdKeyValueDatas[Key]+'''');
                      end;
                      // La valeur est numérique
                      if VarIsNumeric(pdKeyValueDatas[Key]) then
                      begin
                         SQL.Add(sep+Key+' = '+intToStr(pdKeyValueDatas[Key]));
                      end;
     
                      sep := ',';
     
                  end;
     
              SQL.Add('WHERE ID = '+piId);
              SQL.SaveToFile('C:\Debug\Requete.txt');
     
              Try
                 ExecSQL;
                 // On valide la transaction
                 SQLConnection.CommitFreeAndNil(dbxTrans);
              Except
                    on e : tdbxError do begin
                       Showmessage(e.Message);
                       // On va annuler la transaction
                       SQLConnection.RollbackFreeAndNil(dbxTrans);
                    end;
              End;
     
         end;
         pdKeyValueDatas.Free;
    end;
     
    end.
    C'est sur que c'est bete :
    Je teste si le type est entier ou string
    et si c'est un entier, je le parse en string.

    Donc au final, j'utilise toujours du string pour construire la requête...
    C'est que je veux vraiment avoir une méthode où les paramètres pour mettre a jour mes tables sont variables.

  10. #10
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 612
    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 612
    Points : 25 303
    Points
    25 303
    Par défaut
    Citation Envoyé par LaurentC33 Voir le message
    Donc au final, j'utilise toujours du string pour construire la requête...
    C'est que je veux vraiment avoir une méthode où les paramètres pour mettre a jour mes tables sont variables.
    ParamByName ! c'est conçu pour cela !
    On ne s'occupe pas des types, on laisse le Variant et le TParam le faire à notre place !
    En plus, tu n'as pas à générer le SQL complet mais juste l'association Champ\Paramètre, je t'ai fourni le code, il est généralisable totalement, suffit de lui passer une liste de champ pour générer le SQL

    Car tu n'as pas fait le cas le plus pénible : les dates !
    Là ce n'est pas juste ajouter des quote mais transformer un format français par exemple "31/12/2012" en format genre MySQL "2012-12-31" !
    Le TParam.AsDate, le fait à ta place !

    J'ai une petite couche objet qui me fait tout ça, je n'écrit presque jamais mes SQL, je déteste ça !

    Code c++ : 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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    //---------------------------------------------------------------------------
    /**
     * Save Build and Execute a SQL INSERT OR UPDATE Statment
     * @return success or failure
     */
    bool TShaiORPersistent::Save()
    //  throw(EShaiORPersistentError)
    {
      bool Result = false;
     
      if (IsRecordable())
      {
        CheckDataBeforeSave(); // throw EShaiORPersistentError;
     
        bool UpdateMode = IsUpdateModeForSave();
     
        // Construction du SQL - Récupérer la liste des champs !
        TFieldName FieldName;
     
        #pragma region Try Create TStringList
        TStringList *ValuesParamsClause = new TStringList();
        try
        {
          TStringList *FieldsClause = new TStringList();
          try
          {
            TStringList *PropList = new TStringList();
            try
            {
              #pragma end_region
     
              // Génération puis Execution du SQL !
              if (GetPropListForSave(PropList, UpdateMode))
              {
                // Construction de la liste des champs selon INSERT ou UPDATE
                #pragma region Build Field Clause
                FieldsClause->Capacity = PropList->Count;
                if (UpdateMode)
                  ValuesParamsClause->Capacity = PropList->Count;
     
                for (int i = 0; i < PropList->Count; i++)
                {
                  FieldName = this->FieldNameByPropertyName(PropList->Strings[i]);
                  if (UpdateMode)
                  {
                    FieldsClause->Add(ShaiFormatString("%s = %s",
                      (FieldName, ParamNameByPropertyName(PropList->Strings[i], true))));
                  }
                  else
                  {
                    FieldsClause->Add(FieldName);
                    ValuesParamsClause->Add(ParamNameByPropertyName(PropList->Strings[i], true));
                  }
                }
                #pragma end_region
     
                // Fin de la Génération du SQL puis Execution de celui-ci !
                #pragma region Execute SQL Statment
                TSQLQuery* Query = CreateQuery();
                try
                {
                  Query->SQL->BeginUpdate();
                  Query->SQL->Add(FormatSQLForSave(FieldsClause, ValuesParamsClause, UpdateMode));
                  bool UseIdentity = ! UpdateMode && ! IsRelation();
                  if (UseIdentity)
                    Query->SQL->Add(";SELECT @@Identity;"); // @@Identity => Spécifique à Sybase
                  Query->SQL->EndUpdate();
     
                  // Remplissage des Paramètres (Valeurs du INSERT ou UPDATE) !
                  for (int i = 0; i < PropList->Count; i++)
                    Query->ParamByName(ParamNameByPropertyName(PropList->Strings[i]))->Value = GetPropValue(this, PropList->Strings[i], false);
     
                  // Execution du SQL !
                  // Cela peut déclencher une Exception, si elle se produit, je la laisse se propager !
                  GetUtilShai()->RunParameterizedQuery(Query, UseIdentity);
     
                  // Vérifie le bon déroulement de la requête et récupère l'identifiant inséré !
                  if (UseIdentity)
                  {
                    PrimaryID = Query->FieldByName("@@Identity")->AsInteger; // @@Identity => Spécifique à Sybase
                    Result = PrimaryID;
                  }
                  else
                  {
                    Result = (Query->RowsAffected == 1);
                  }
                }
                __finally
                {
                  delete Query;
                }
                #pragma end_region 
              }
        #pragma region Finally Free TStringList
            }
            __finally
            {
              delete PropList;
            }
          }
          __finally
          {
            delete FieldsClause;
          }
        }
        __finally
        {
          delete ValuesParamsClause;
        }
        #pragma end_region
      }
     
      return Result;
    }
    //---------------------------------------------------------------------------
    String TShaiORPersistent::FormatSQLForSave(TStrings *FieldsClause, TStrings *ValuesParamsClause, bool UpdateMode)
    {
      String Result;
     
      if (UpdateMode)
      {
        FieldsClause->Delimiter = ',';
        FieldsClause->QuoteChar = ' ';
        Result = ShaiFormatString("UPDATE %s \nSET %s \nWHERE %s",
          (TableName, FieldsClause->DelimitedText, BuildWhereConditionForSaveUpdate()));
      }
      else
      {
        ValuesParamsClause->Delimiter = ',';
        ValuesParamsClause->QuoteChar = ' ';
        Result = ShaiFormatString("INSERT INTO %s (%s) \nVALUES (%s)",
          (TableName, FieldsClause->CommaText, ValuesParamsClause->DelimitedText));
      }
     
      return Result;
    }
     
    //---------------------------------------------------------------------------
    TShaiORPersistent::TFieldName TShaiORPersistent::ParamNameByPropertyName(const TPropertyName APropertyName, bool AddColonPrefix/* = false*/)
    {
      if (AddColonPrefix)
        return ":Param_" + APropertyName;
      else
        return "Param_" + APropertyName;
    }
     
    //---------------------------------------------------------------------------
    bool TShaiORPersistent::GetPropListForSave(TStrings *PropList, bool UpdateMode)
    {
      bool Result = false;
     
      if (TShaiRTTIWrapper::GetPersistentProperties(this->ClassType(), PropList))
      {
        if (IsRelation())
        {
          // Pour une clé composée, on insère leur valeur la première fois mais pas de mise à jour !
          if (UpdateMode)
          {
            for (int i = 0; i < FMetaData->RelationMemberProperties->Count; i++)
              PropList->Delete(PropList->IndexOf(FMetaData->RelationMemberProperties->Strings[i])); // IndexOf Ne peut pas échouer !
          }
        }
        else
          PropList->Delete(PropList->IndexOf(TShaiORPersistent::PROP_NAME_PRIMARY_ID)); // IndexOf Ne peut pas échouer !
     
        Result = PropList->Count;
      }
     
      return Result;
    }

    après j'ai des objets déclarés ainsi :

    Code c++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class TShaiComputer : public TShaiORPersistent
    {
      typedef TShaiORPersistent inherited;
     
    ...
     
    __published:
      // Propriétés Publiées
      __property PrimaryID; // Augmentation de Visibilité
      __property String ComputerName = {read=GetComputerName, write=SetComputerName};
      __property int ComputerNumber = {read=GetComputerNumber, write=SetComputerNumber};
      __property String NetworkName = {read=GetNetworkName, write=SetNetworkName};
    };

    Qui s'utilise ainsi

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    TShaiComputer NouveauPoste = TShaiComputer();
    NouveauPoste->ComputerName = "Machin";
    NouveauPoste->ComputerNumber = 666; 
    NouveauPoste->NetworkName = "\\TrucMuche";
    NouveauPoste->Save();

  11. #11
    Membre expérimenté

    Homme Profil pro
    Inscrit en
    Mars 2004
    Messages
    897
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2004
    Messages : 897
    Points : 1 561
    Points
    1 561
    Par défaut
    Citation Envoyé par ShaiLeTroll Voir le message
    Le Champ est typé
    Par contre Paul TOTH, ton histoire de avec ou sans quote, n'est-ce pas justement les quote qui donne le type en SQL ?
    Ce n'est pas très clair comme propos !
    Bien au contraire, la remarque de Paul est plus que pertinente.

    En effet, nous ne connaissons pas tous les traitements que notre ami effectue en amont avec les variants.
    Nous savons seulement que la valeur finale de chaque variant servira de paramètre dans une requête SQL et que selon l’alternative concernant le type il faudra formater une variable string avec ou sans ‘quotes’.

    Partant de ce principe, ShaiLeTroll, il existe des cas qui se révèlent indécidables d’où la remarque de Paul.
    En effet, supposons qu’en amont il existe un traitement sur ce variant qui intègre une fonction F effectuant un calcul symbolique. Donc pour tout x de l’ensemble des entrées possibles (réel où chaîne de caractères pour simplifier), F(x) peut être soit un réel soit une chaîne de caractères ce qui rend la valeur de F indécidable quant à son type.

    Par exemple : si x = t+3, F(x) = ‘t+3’ on peut facilement décider du type qui est un string, en revanche si x = 2+3, F(x) = 5 ou F(x) = ‘5’ et ce cas est indécidable sans un traitement annexe. (ie la fonction VarIsFloat avec ‘5’ « échoue » soit encore elle ne répond pas convenablement).

    Voici une réponse possible pour lever l’indécidabilité si en amont des traitements font surgir ce problè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
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    const
      PIChar          = '¶';
      ExponantielChar = 'e';
     
    type
      TStrFloatType = (sfFloat,sfString);
     
      PStrFloatRec = ^StrFloatRec;
      StrFloatRec = packed record
        kind : TStrFloatType;
        Flt  : double;
        Str  : string;
      end;
    .......................
    function GetNumeric(AText: string; var Value: double):boolean;
    {TODO: Cette méthode pourrait être étendue via option pour traiter les chaînes
     numériques de type expression.
    }
    var
      I: Integer;
     
      function SpecialCases:boolean;
      {Cas spéciaux inclus sans options. Gestion l'équivalent des valeurs numériques
       pour Pi et exponantielle.
       ATTENTION : Pi doit être symbolisé par son caractère PiChar "SymbolConst".}
      begin
        Result := True;
        if Length(AText) = 1 then begin
          if AText = PiChar then Value := Pi
          else
          if AText = ExponantielChar then Value := Exp(1)
          else Result := False;
        end
        else
        if (Length(AText) = 2) and (AText[1] = '-') then begin
          if AText[2] = PiChar then Value := -Pi
          else
          if AText[2] = ExponantielChar then Value := -Exp(1)
          else Result := False;
        end
        else Result := False;
      end;
    begin
      DecimalSeparator := '.';
      {Initialisé à false pour que e et pi soient considérés comme étant
       des constantes afin de les manipuler comme les variables.}
      Result := False;
      if SpecialCases then Exit;
      Result := True;
      I := 0;
      if AText[1] = '-' then Inc(I);
      {Récupérer les caratères "chiffres" tant que possible}
      repeat
        Inc(I);
        {Pour la partie entière}
        while (AText[I] in ['0'..'9','.']) and (I <= Length(AText)) do
          if AText[I] = '.' then begin
            Inc(I);
            Break;
          end
          else Inc(I);
        {Pour la partie décimale}
        while (AText[I] in ['0'..'9']) and (I <= Length(AText)) do Inc(I);
        {La chaîne complète n'est pas numérique}
        if I <= Length(AText) then  begin
          Result := False;
          Exit;
        end;
      until (I >= Length(AText));
      {Aucune erreur n'a été rencontrée donc on évalue numériquement la chaîne.
       Aucune d'exception n'est levée dans le cas où la chaîne n'est pas correcte.}
      if Result then begin
        Value := StrToFloat(AText);
      end;
    end;
     
    function IsNumeric(const AText: string):boolean;
    var
      Value: Double;
    begin
      Result := GetNumeric(AText,Value);
    end;
     
    procedure VariantToStrFloat(const Source: variant; var Destination: StrFloatRec);
    begin
      if IsNumeric(Source) then begin
        Destination.kind := sfFloat;
        Destination.Flt  := Source;
      end
      else begin
        Destination.kind := sfString;
        Destination.Str  := Source;
      end;
    end;
     
    procedure StrFloatToVariant(const Source: StrFloatRec; var Destination: variant);
    begin
      case Source.kind of
        sfFloat  : Destination := Source.Flt;
        sfString : Destination := Source.Str;
      end;
    end;
    La fonction IsNumeric doit lever l'indécidabilité.

    Utilisation des API.

    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
    TValuableItem = class
      private
        FValue    : StrFloatRec;
      protected
        function  GetValue:Variant;
        procedure SetValue(const AValue: Variant);
      public
        property  Value: Variant read GetValue write SetValue;
      end;
    ..............
    function TValuableItem.GetValue: Variant;
    begin
      StrFloatToVariant(FValue,Result);
    end;
     
    procedure TValuableItem.SetValue(const AValue: Variant);
    begin
      VariantToStrFloat(AValue,FValue);
    end;

  12. #12
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 457
    Points
    28 457
    Par défaut
    Citation Envoyé par ShaiLeTroll Voir le message
    Le Paramètre d'un SQL doit être typé
    La Valeur c'est déjà plus confus, personnellement ayant des objets métiers, c'est fortement typé et en phase avec la DB

    Cet amalgame de typer fortement les valeurs dans son code orienté DB me semble un grand classique que l'on hérite du TDataSet avec TIntegerField, TStringField ...


    Peut-être que son SGBD ne fait pas de conversion automatique des types, et qu'il est obligé d'écrire un SQL fortement typé conforme à son schéma !

    Par contre Paul TOTH, ton histoire de avec ou sans quote, n'est-ce pas justement les quote qui donne le type en SQL ?
    Ce n'est pas très clair comme propos !

    Dans l'histoire, je ne vois pas ce qui te choques, que soit avec FieldByName ou ParamByName, j'utilise systématiquement AsString, AsInteger, AsDate... selon mon schéma, j'évite les mélanges de type !
    j'explique autrement

    en fait ma réflexion me vient de ce que je fais en PHP, rien à voir avec TFieldXXX

    si on reprend l'exemple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    TslallowedFields = TArray<string>.Create('Nom, Prenom, Date_Naissance, Adresse, Code_Postal')
    nous avons probablement des types String, String, Date, String et String, peu importe la nature des valeurs qu'on nous passe en paramètre ils doivent correspondre à ces types. Si ce n'est pas le cas la fonction devrait lever une exception. On peut se reposer que le moteur BDD pour ce contrôle, mais si tu prend SQLite par exemple, il accepte toute valeur dans tout champ, le type n'est qu'indicatif

    avec des paramètres tu masques l’ambiguïté pour construire ta requête, mais là aussi le moteur BDD te lèvera une exception si la valeur n'est pas du type attendu...ou pas

  13. #13
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 612
    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 612
    Points : 25 303
    Points
    25 303
    Par défaut
    Citation Envoyé par Pascal Jankowski Voir le message
    Bien au contraire, la remarque de Paul est plus que pertinente.

    Partant de ce principe, ShaiLeTroll, il existe des cas qui se révèlent indécidables d’où la remarque de Paul.
    Pertinente oui, mais on ne connait pas le SGBD de LaurentC33 , vous présumez des capacités de conversion de son SGBD comme le fait MySQL ou Sybase entre chaine\nombre ou comme SQLite qui si j'ai bien compris accepte tout ce qui peut polluer la DB de données imprévues

    indécidabilité : c'est bien le problème !

    On ignore effectivement la source des données, possible que tout soit émis en chaine, si le SGBD sait convertir tout seul une chaine dans un SQL affecté à un nombre, on peut le laisser faire effectivement

    @LaurentC33, teste le mais tu peux tout mettre entre quote aussi bien chaine ou integer, d'expérience MySQL et Sybase font tout seul la conversion, tu peux te simplifier la vie et faire confiance au SGBD ... avec les risques de sécurités que cela peut introduire


    Une appli construite en OO n'a pas ce problème, puisque tout accès à la DB est faite via un objet métier unique !
    C'est l'objet métier qui peut assurer la validité de la donnée (type, format, espace...) avant de l'envoyer à la DB
    C'est l'objet métier qui assure lui même certaines conversions

    Cet objet métier peut-être l'interface d'un Serveur applicatif DataSnap !

    la DB elle peut ajouter des contraintes supplémentaires, lorsque l'on fonctionne dans un environnement multi-SGBD,
    on ne peut faire tout de la même façon, le SQL, les Triggers, les Constraints ont différentes limites, du coup,
    C'est au code OO de gérer cela en amont !
    Il est évidemment facile de laisser la DB tout faire mais est-ce efficient ?

    De plus, LaurentC33 semble développer un serveur applicatif métier, TServerMethods2 fait penser à une 2nde classe dans un serveur DataSnap

    En utilisant JSON ou SOAP pour le dialogue Client\Serveur, on peut aussi avoir des données fortement typés donc assurer le bon type tout au long du flux ou alors assurer la conversion

    ModifClient permet de mettre à jour un nombre de champ variable avec des valeurs variantes qui n'ont pas forcément le bon type, il est possible que le client ne soit pas même sa propre application !
    Et cette histoire d'indécidabilité, faut juste faire un choix :
    - le programme converti et gère les erreurs émis par le code et par le SGBD
    - le programme envoi au SGBD qui se débrouille et renvoi une erreur

    La méthode ModifClient doit pouvoir renvoyer les erreurs de modification, les arguments inconnus, les valeurs incorrectes ... !
    Sans oublier de protéger contre l'injection de SQL, en laissant libre les valeurs chaines avec du SQL concaténé, on peut injecter du SQL avec de genre de syntaxe '; DROP DATABASE; # !
    Dès Site Web mal protégé sur le formulaire de login\pw pouvait être ouvert en mode admin avec de l'injection SQL, d'où les paramètres ou des fonctions comme Escape_String

    L'utilisation de Paramètre qui force le typage et donc implique une conversion explicite !
    C'est juste un choix technique !
    C'est à LaurentC33 de voir ce qu'il choisi


    Mais pourquoi solliciter le SGBD alors que l'on peut savoir dans le code qu'il va y avoir une erreur de conversion ?
    l'indécidabilité, on peut faire en sorte de la supprimer en explicitant les conversions
    On connait le type de destination :
    - soit codé en dur,
    - soit via un XML décrivant le schéma,
    - soit via un DESCRIBE,
    - soit via un SELECT * WHERE 1=0 donnant la structure TDataSet typé ...
    - ...

    Il y a quelques années, dans un ancien emploi, j'ai fait une couche objet métier utilisant que des Variants, un XML décrivait les objets, ce même XML servait aussi pour les mises à jour du schéma, ainsi binaire et DB était toujours en accord sur le même schéma !

    la couche objet métier se chargeait automatiquement de convertir le variant vers le type de la DB, classique lorsque l'on utilise des TEdit et des valeurs Numeriques, cela gérait évidemment les erreurs de conversion via des exception (avec le plus d'info possible pour aider au débug ou fournir des messages utilisateurs explicites)

    Actuellement, je n'ai pas poussé si loin, je type mes property en dur dans le code selon le même type que celui qui j'ai défini dans les CREATE\ALTER TABLE, c'est juste un peu de rigueur de faire évoluer les deux ensembles

    Cela a un coût d'effectuer un SQL voué à l'échec, un coût Réseau et CPU sur le client et le serveur !

    Citation Envoyé par Paul TOTH Voir le message
    avec des paramètres tu masques l’ambiguïté pour construire ta requête, mais là aussi le moteur BDD te lèvera une exception si la valeur n'est pas du type attendu...ou pas
    si l'on utilise un paramètre fortement typé genre AsDate, cela force la conversion par code, du coup on peut provoque l'exception plus tôt au lieu d'émettre le SQL au serveur !
    Encore une fois, c'est juste une histoire de préférence !

    Actuellement, je n'ai pas ce problème avec mes TParam.Value par ce que mes objets sont fortement typés, on peut pas affecter à ces objets autre chose !
    Mais j'ai bien connu ce problème de confusion type de la valeur (souvent chaine issu d'un TControl) et celle de la DB, j'ai pris le partie d'avoir un code robuste aux erreurs et le plus efficient que possible en évitant des instructions inutiles

  14. #14
    Membre habitué

    Homme Profil pro
    Développeur multimédia
    Inscrit en
    Février 2013
    Messages
    148
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Landes (Aquitaine)

    Informations professionnelles :
    Activité : Développeur multimédia
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Février 2013
    Messages : 148
    Points : 199
    Points
    199
    Par défaut
    De plus, LaurentC33 semble développer un serveur applicatif métier, TServerMethods2 fait penser à une 2nde classe dans un serveur DataSnap
    Exactement

    Je vais revenir très vite vers vous pour vous indiquer la méthode que j'ai utilisée.
    Merci en tout cas pour toute vos manifestations, c'est très intéressant !

Discussions similaires

  1. Réponses: 6
    Dernier message: 24/11/2009, 11h38
  2. Attribut de type enum dans un objet persistant
    Par herve91 dans le forum JPA
    Réponses: 2
    Dernier message: 16/08/2008, 21h02
  3. [POO] Récupérer le type d'une variable Objet
    Par whitespirit dans le forum Langage
    Réponses: 4
    Dernier message: 10/06/2008, 10h13
  4. Récupérer chaque ligne de type enregistrement dans une table
    Par atporfi dans le forum Administration
    Réponses: 2
    Dernier message: 02/04/2008, 19h12
  5. Réponses: 4
    Dernier message: 28/03/2007, 17h27

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