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 :

Comment trier un TClientDataSet sur un champ fkLookup ?


Sujet :

Bases de données Delphi

  1. #1
    Membre à l'essai
    Profil pro
    Inscrit en
    Février 2004
    Messages
    39
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 39
    Points : 20
    Points
    20
    Par défaut Comment trier un TClientDataSet sur un champ fkLookup ?
    Bonjour

    mon besoin : je cherche a trier un TClientDataSet sur un champ de type fkLookup

    ma méthode : sur le TClientDataSet, je tente un AddIndex en mentionnant le nom du champ lookup :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    MonClientDataSet.AddIndex('MonIndex', 'MonChampLookup', [])
    mon probleme : j'obtiens l'erreur d'execution suivante :

    Project MonProjet.exe raised exception class EDatabaseError with message 'MonClientDataSet: Field 'MonChampLookup' not found'.

    evidemment le champ existe bel et bien, et si je tente le AddIndex sur le nom d'un champ "normal" de type fkData, je n'ai pas l'erreur

    Ma question 1 : connaissez vous cette impossibilité de faire un AddIndex sur un champ fkLookup ?
    Ma question 2 : quelle autre méthode que AddIndex me conseillez vous pour trier le ClientDataSet ?

    merci pour votre aide !

    anthony

  2. #2
    Expert confirmé

    Profil pro
    Leader Technique
    Inscrit en
    Juin 2005
    Messages
    1 756
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France, Rhône (Rhône Alpes)

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

    Informations forums :
    Inscription : Juin 2005
    Messages : 1 756
    Points : 4 173
    Points
    4 173
    Par défaut
    Le problème c'est que le champ Lookup est un champ calculé. Hors le ClientDataSet ne stocke pas les champs calculés, ils sont calculés dynamiquement lorsqu'on se positionne sur l'enregistrement.
    Donc il ne peut pas indexer le champ.

    Tu peux essayer de créer l'index sur le champ qui sert à calculer le lookup. Ca te permettra de trier les données dans l'ordre du code, mais pas dans l'ordre du libellé associé.

    Autre solution, au lieu de créer un champ calculé, essaye d'en faire un InternalCalc. Ces derniers sont stockés dans le cache de données il me semble. Tu pourras peut-être l'indexer ensuite. Mais je n'ai jamais essayer, ce n'est peut-être pas possible comme ça.

    Sinon autre solution, tu ajoute une colonne "ordre de tri" qui contient un entier. Tu tries tes données à la main selon la valeur du lookup et tu définis la colonne "ordre de tri" pour que la valeur indique l'ordre des enregistrements voulus. Il ne te restera plus qu'à définir l'index sur cette colonne "ordre de tri".

    Enfin, au lieu de faire un champ lookup, tu initialises ton clientdataset à partir d'une query qui a elle même calculée le lookup (une jointure sur la table des libellés...). de cette façon, le lookup devient un champ ordinaire que tu peux indexer comme n'importe quel autre.

  3. #3
    Membre à l'essai
    Profil pro
    Inscrit en
    Février 2004
    Messages
    39
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 39
    Points : 20
    Points
    20
    Par défaut
    merci pour ta reponse

    Citation Envoyé par Franck SORIANO Voir le message
    Tu peux essayer de créer l'index sur le champ qui sert à calculer le lookup. Ca te permettra de trier les données dans l'ordre du code, mais pas dans l'ordre du libellé associé.
    cette solution ne me conviendra pas car c'est vraiment selon le lib que je souhaite trier, et non pas de son code associé
    Citation Envoyé par Franck SORIANO Voir le message
    Autre solution, au lieu de créer un champ calculé, essaye d'en faire un InternalCalc. Ces derniers sont stockés dans le cache de données il me semble. Tu pourras peut-être l'indexer ensuite. Mais je n'ai jamais essayer, ce n'est peut-être pas possible comme ça.
    en fait actuellement ce n'est pas des champs fkCalculated que j'ai, mais des fkLookup : donc l'utilisation de champs InternalCalc ne peut malheureusement pas m'aider
    Citation Envoyé par Franck SORIANO Voir le message
    Sinon autre solution, tu ajoute une colonne "ordre de tri" qui contient un entier. Tu tries tes données à la main selon la valeur du lookup et tu définis la colonne "ordre de tri" pour que la valeur indique l'ordre des enregistrements voulus. Il ne te restera plus qu'à définir l'index sur cette colonne "ordre de tri".
    Je n'ai pas précisé que mon ClientDataSet est cablé sur un DatasetProvider distant (appli 3 tiers), et que je fais des ApplyUpdates directement sur le Dataset : donc si je rajoute le champ "ordre de tri", mon ApplyUpdates va merder, et je devrais ajouter du code coté serveur dans le OnBeforeUpdateRecord, charge que je veux eviter ...
    Citation Envoyé par Franck SORIANO Voir le message
    Enfin, au lieu de faire un champ lookup, tu initialises ton clientdataset à partir d'une query qui a elle même calculée le lookup (une jointure sur la table des libellés...). de cette façon, le lookup devient un champ ordinaire que tu peux indexer comme n'importe quel autre.
    meme remarque que precedemment : si je fais ca mes applyUpdates ne fonctionneront plus tels quels !

    je pense que je vais partir sur la solution suivante : lorsque l'user clique sur un entete de colonne correspondant a un champ fkLookup, pour trier sur ce champ, je vais :
    - rajouter dynamiquement ds le dataset un champ contenant le libellé affiché ds la colonne cliquée,
    - puis je ferai mon AddIndex sur cette colonne temporaire,
    - ensuite je supprimerai la colonne temporaire
    - et mon dataset restera trié (j'espere !)

    merci encore pour ta reponse

    anthony

  4. #4
    Membre à l'essai
    Profil pro
    Inscrit en
    Février 2004
    Messages
    39
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 39
    Points : 20
    Points
    20
    Par défaut
    J'ai résolu mon probleme : dans une grille basée sur un dataset contenant des champs fkLookup et des champs fkData, pouvoir cliquer sur l'entete d'un des champs fkLookup afin de trier selon ce champ

    => Suite a la lecture de cet article, et specialement du commentaire (*) de "Dave Rowntree on Apr 02 2003" en bas, j'ai créé la classe TDatasetSortingUtility suivante :

    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
    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
    unit UDatasetSortingUtility;
     
    interface
     
    uses DB, dbClient, cxGridTableView, cxGridDBTableView;
     
    type TDatasetSortingUtility = class
      const prefix : string = 't_';
      procedure FillInternalCalcFields(DataSet: TDataSet);
      class procedure AddInternalCalcFields(DataSet: TDataSet);
      class procedure SortAGrid(GridTableView: TcxGridTableView; AColumn: TcxGridColumn);
      class function SortCustomClientDataSet(DataSet: TCustomClientDataSet; FieldName: String): Boolean;
      private
        FOriginalOnCalcFields : Procedure(Dataset : Tdataset);
        constructor Create(Dataset : Tdataset);
    end;
     
     
    implementation
     
    uses TypInfo, Dialogs;
     
    { TDatasetSortingUtility }
     
    constructor TDatasetSortingUtility.Create(Dataset: Tdataset);
    begin
      if Assigned(DataSet.OnCalcFields) then
        // si le dataset a deja un gestionnaire OnCalcFields assigné, on stocke un pointeur vers ce gestionnaire
        FOriginalOnCalcFields := @DataSet.OnCalcFields
      else
        FOriginalOnCalcFields := nil;
    end;
     
    procedure TDatasetSortingUtility.FillInternalCalcFields(DataSet: TDataSet);
    var
      i:integer;
      currentLookupField, correspIntCalcField:TField;
    begin
      // si le dataset avait deja un gestionnaire OnCalcFields assigné, on commence par executer ce gestionnaire
      if Assigned(FOriginalOnCalcFields) then
        FOriginalOnCalcFields(Dataset);
     
      // maintenant, pour chaque champ fkLookup du dataset, on actualise son champ homologue de type fkInternalCalc 
      for i := 0 to Pred(DataSet.Fields.Count) do begin
        currentLookupField := DataSet.Fields[i];
        if currentLookupField.FieldKind <> fkLookup then continue;
     
        correspIntCalcField := DataSet.FindField(prefix+currentLookupField.FieldName);
        if correspIntCalcField <> nil then
          correspIntCalcField.Value :=
            currentLookupField.LookupDataSet.Lookup(
              currentLookupField.LookupKeyFields,
              DataSet.FieldByName(currentLookupField.KeyFields).Value,
              currentLookupField.LookupResultField);
      end;
    end;
     
    class procedure TDatasetSortingUtility.AddInternalCalcFields(DataSet: TDataSet);
    var
      i:integer;
      currentField:TField;
      internalCalcField:TStringField;
      dummy : TDatasetSortingUtility;
    begin
      // on parcourt chaque champ du dataset, et si l'on trouve un champ de type fkLookup, on lui crée un
      // homologue de type fkInternalCalc, et s'appelant prefix + le FieldName original
      for i := 0 to Pred(DataSet.Fields.Count) do begin
        currentField := DataSet.Fields[i];
        if currentField.FieldKind <> fkLookup then continue;
        internalCalcField := TStringField.Create(DataSet);
        internalCalcField.Name := prefix+currentField.FieldName;
        internalCalcField.FieldKind := fkInternalCalc;
        internalCalcField.FieldName := prefix+currentField.FieldName;
        internalCalcField.DataSet := DataSet;
      end;
      // maintenant on va créer un objet TDatasetSortingUtility associé au dataset, puis affecter au OnCalcFields
      // du dataset la méthode d'instance FillInternalCalcFields
      dummy := TDatasetSortingUtility.Create(DataSet);
      DataSet.OnCalcFields := dummy.FillInternalCalcFields;
    end;
     
    class procedure TDatasetSortingUtility.SortAGrid(GridTableView: TcxGridTableView; AColumn: TcxGridColumn);
    Var
      DatasetToSort : TClientDataset;
      FieldToSort : string;
    begin
      DatasetToSort :=  TcxGridDBTableView(GridTableView).Datacontroller.Datasource.dataset as TclientDataset;
      FieldToSort   :=  TcxGridDBColumn(AColumn).DataBinding.FieldName;
      SortCustomClientDataSet(DatasetToSort,FieldToSort);
    end;
     
    // cf. article Understanding ClientDataSet Indexes ( http://dn.codegear.com/article/29056 )
    class function TDatasetSortingUtility.SortCustomClientDataSet(DataSet: TCustomClientDataSet;
      FieldName: String): Boolean;
    var
      i: Integer;
      IndexDefs: TIndexDefs;
      IndexName: String;
      IndexOptions: TIndexOptions;
      Field: TField;
      onCalcFields : TDataSetNotifyEvent;
    begin
      Result := False;
      Field := DataSet.Fields.FindField(FieldName);
      //If invalid field name, exit.
      if Field = nil then Exit;
      //if invalid field type, exit.
      if (Field is TObjectField) or (Field is TBlobField) or
        (Field is TAggregateField) or (Field is TVariantField)
         or (Field is TBinaryField) then Exit;
     
      // si le champ est de type fkLookup il faut effectuer le tri sur un champ homologue de type fkInternalCalc
      // ce champ devra avoir été créé au prealable grace a la proc AddInternalCalcFields
      if Field.FieldKind = fkLookup then begin
        if DataSet.Fields.FindField(prefix+FieldName) = nil then begin
          ShowMessage('Sorting is impossible on field "'+FieldName+'"'+chr(13)+chr(13)+
            'Developer must call TDatasetSortingUtility.AddInternalCalcFields on dataset '+ DataSet.Owner.Name+'.'+DataSet.name+' first ...');
          exit;
        end else
          FieldName := prefix+FieldName;
      end;
     
      //Get IndexDefs and IndexName using RTTI
      if IsPublishedProp(DataSet, 'IndexDefs') then
        IndexDefs := GetObjectProp(DataSet, 'IndexDefs') as TIndexDefs
      else
        Exit;
     
      if IsPublishedProp(DataSet, 'IndexName') then
        IndexName := GetStrProp(DataSet, 'IndexName')
      else
        Exit;
      //Ensure IndexDefs is up-to-date
      IndexDefs.Update;
      //If an ascending index is already in use,
      //switch to a descending index
      if IndexName = FieldName + '__IdxA' then begin
        IndexName := FieldName + '__IdxD';
        IndexOptions := [ixDescending];
      end else begin
        IndexName := FieldName + '__IdxA';
        IndexOptions := [];
      end;
      //Look for existing index
      for i := 0 to Pred(IndexDefs.Count) do begin
        if IndexDefs[i].Name = IndexName then begin
          Result := True;
          Break
        end;
      end;
      //If existing index not found, create one
      if not Result then begin
        DataSet.AddIndex(IndexName, FieldName, IndexOptions);
        Result := True;
      end;
      //Set the index
      onCalcFields := DataSet.OnCalcFields;
      DataSet.OnCalcFields := nil;
      SetStrProp(DataSet, 'IndexName', IndexName);
      DataSet.OnCalcFields := onCalcFields;
    end;
     
    end.
    => Lors de la création du datamodule contenant mon DatasetAvecChampsLookup, j'appelle la méthode de classe suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    TDatasetSortingUtility.AddInternalCalcFields(DatasetAvecChampsLookup);
    => Une fois mis en place, le gestionnaire d'evenement suivant permet lors du clic sur l'en tete de ma grille developerExpress, d'appeler la méthode de classe SortAGrid, en lui fournissant la grille a l'origine du clic, et la colonne cliquée. A partir de ces 2 entrées, le tri va se faire sur le dataset sous-jacent

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    procedure TfrmRef_Equip.CustomColumnHeaderClick(Sender: TcxGridTableView;
      AColumn: TcxGridColumn);
    begin
      TDatasetSortingUtility.SortAGrid(Sender, Acolumn);
    end;
    Anthony





    (*) ce commentaire explique que s'il est bien impossible de mettre un index sur un champ de type fkLookup, on peut en revanche passer par un champ "homologue" au fkLookup, lui de type fkInternalCalc, sur lequel on peut alors sans probleme ajouter un index

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 4
    Dernier message: 27/07/2009, 18h09
  2. [CR10] Trier un état sur un champs calculé
    Par alpachico dans le forum SAP Crystal Reports
    Réponses: 2
    Dernier message: 06/09/2006, 12h04
  3. Comment faire un Tri sur plusieurs champs
    Par guile153 dans le forum Langage SQL
    Réponses: 2
    Dernier message: 24/07/2006, 12h52
  4. [Access] Comment gérer le filtrage sur un champs DateTime ?
    Par mappy dans le forum Accès aux données
    Réponses: 7
    Dernier message: 12/07/2006, 14h49
  5. [ADO.NET]Comment réaliser une relation sur plusieurs champs?
    Par kleomas dans le forum Accès aux données
    Réponses: 3
    Dernier message: 13/03/2006, 12h40

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