• Aucun résultat trouvé

Cette deuxième approche est beaucoup plus claire. Elle ressemble à l'approche ADO.NET de programmation. Dans ADO.NET on utilise des commandes paramétrées pour agir sur la source de données. Il existe de nombreuses méthodes pour aborder ce type de stratégies que j'aborderai dans un autre article sur ADO.NET mais nous allons étudier le principe général ainsi que les pièges à éviter.

A la base, soit on travaille avec une commande "à la volée" soit on passe par des fonctions. Les deux techniques présentent des avantages, mais dans le cas qui m'intéresse, je vais utiliser des fonctions. Dans ADO.NET on gère autant de commande que de requête action, il n'y a alors plus qu'à passer les paramètres à la commande pour gérer celle-ci. Dans une approche plus évoluée, je peux générer une commande entièrement modulable, donc similaire au moteur de curseur en utilisant un code de type ADO.NET.

Prenons l'exemple suivant qui gère une requête de modifications. procedure TForm1.btnTraiteClick(Sender: TObject); var adoRecordset:TADODataset; adoConnection:TADOConnection; RecValue: integer; TabParam:array of variant; begin randomize; adoConnection:=TADOConnection.Create(Owner); with adoConnection do begin

ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\Biblio.mdb;'; CursorLocation:=clUseClient; Connected:=True; end; adoRecordset:=TADODataset.Create(Owner); with adoRecordset do begin

Connection:=adoConnection; CursorType:=ctStatic;

LockType:=ltBatchOptimistic; CommandType:= cmdText;

CommandText:='SELECT * FROM Authors'; Active:=True; Recordset.Properties['Update Resync'].Value:=2; First; MaCommand:=TADOCommand.Create(nil); MaCommand.Connection:=adoConnection; TabParam:=vararrayof(['bidou',fieldvalues['Au_Id']]); RecValue:=CommUpdate(adoconnection,TabParam); if recvalue=0 then showmessage('pas de correspondance'); end; end;

function TForm1.CommUpdate(const ListeParam: array of variant):integer;

var ParamRequete array of variant; compteur: integer;

begin

With MaCommand do begin

CommandText:='UPDATE Authors SET [author]=:NewValChamp WHERE Au_Id=:ValCle';

CommandType:=cmdtext;

Execute(result,vararrayof(ListeParam)); end;

end;

Je suis là dans un codage standard ADO.NET qui gère une commande par fonction. C'est un code très sur mais pas très souple puisque si je veux pouvoir modifier de nombreuses tables je vais me retrouver à gérer autant de fonctions que de tables pour un seul type d'action. Je peux donc concevoir

une fonction beaucoup plus puissante, répondant aux modifications de plusieurs tables. Toujours dans le même ordre d'idée, je vais m'astreindre à travailler dans le respect strict de la concurrence optimiste.

Donc j'utilise une fonction comme celle ci dessous

function TForm1.CommUpdate(const ListeParam: array of variant; const GestParam: integer):integer;

var ParamRequete: variant; compteur: integer; begin MaCommand.CommandType:=cmdtext; if GestParam=0 then MaCommand.Execute(result,vararrayof(ListeParam)) else begin

MaCommand.CommandText:='UPDATE ' + ListeParam[0] + ' SET ';

ParamRequete:=VarArrayCreate([0,Trunc(length(ListeParam)/2)- 1],varVariant);

compteur:=1;

MaCommand.CommandText:=MaCommand.CommandText+ '[' + ListeParam[compteur]+ ']=:param' + IntToStr(compteur);

compteur:=compteur+1;

While compteur<=GestParam do begin

MaCommand.CommandText:=MaCommand.CommandText+ ' AND [' + ListeParam[compteur]+ ']=:param' + IntToStr(compteur);

compteur:=compteur+1; end;

MaCommand.CommandText:=MaCommand.CommandText+ ' WHERE [' + ListeParam[compteur]+ ']=:param' + IntToStr(compteur);

compteur:=compteur+1;

While compteur<=Trunc(length(ListeParam)/2) do begin

MaCommand.CommandText:=MaCommand.CommandText+ ' AND [' + ListeParam[compteur]+ ']=:param' + IntToStr(compteur);

compteur:=compteur+1; end;

for compteur:= 0 to Trunc(length(ListeParam)/2)-1 do ParamRequete[compteur]:=listeparam[Trunc(length(ListeParam)/2)+1+com pteur]; MaCommand.Execute(result,ParamRequete) end; end;

Une telle fonction attend comme paramètre :

Un tableau de variant contenant le nom de la table puis les noms des champs modifiés puis les noms des champs, puis la liste des valeurs de ces champs dans le même ordre.

Un entier représentant le nombre de champs modifiés. Si cet entier vaut zéro, le tableau de variant ne contiendra que la liste des valeurs.

Je sais bien que ce style de programmation semble affreusement tordu, mais comme vous l'avez maintenant remarqué, j'adore ça. Plus sérieusement, cela est aussi très efficace. Bon, où en étais- je avant d'être brutalement interrompu par votre remarque sur ma façon de coder.

Ah! oui. Un appel explicite de cette fonction dans le cadre de ma table 'Authors' pourrait être : TabParam:=VarArrayOf(['authors','author','Au_Id','author','year born','bidou',fieldvalues['au_id'],fieldvalues['author'],fieldvalues ['year born']]);

Voilà qui devrait finir de déprimer ceux qui ont pu lire jusque là. Evidemment dans le code on n'écrit pas chaque appel comme celui-là, à moins de vouloir se transformer rapidement en chèvre.

Prenons de l'avance sur mon exemple suivant et imaginons que nous souhaitions ajouter une valeur aléatoire à l'année de naissance.

La modification est de la forme

CommandText:=''SELECT * FROM Authors WHERE [year born] IS NOT NULL'; Active:=True;

Recordset.Properties['Update Resync'].Value:=2; First;

MaCommand:=TADOCommand.Create(nil); MaCommand.Connection:=adoConnection; while not Eof do begin

Edit; FieldByName('year born').Value:=adoRecordset.FieldByName('year born').Value+RandomRange(1,5); Post; Next; end;

Dans le cas de ma fonction je devrais utiliser

CommandText:='SELECT * FROM Authors WHERE [year born] IS NOT NULL'; Active:=True; Recordset.Properties['Update Resync'].Value:=2; First; MaCommand:=TADOCommand.Create(nil); MaCommand.Connection:=adoConnection; SetLength(TabParam,3+fieldcount*2);

tabparam[0]:='authors'; // nom de la table

tabparam[1]:='year born'; // nom du champ modifiés tabparam[2+fieldcount]:=FieldByName('year

born').Value+RandomRange(1,5);

for compteur:=0 to fieldcount-1 do begin

tabparam[compteur+2]:=recordset.Fields[compteur].Name; tabparam[compteur+3+fieldcount]:=Fields[compteur].AsVariant; end; RecValue:=CommUpdate(TabParam,1); if recvalue=0 then showmessage('pas de correspondance'); Next; setlength(TabParam,4); while not Eof do begin

tabparam[0]:=FieldByName('year born').Value+RandomRange(1,5); for compteur:=0 to fieldcount-1 do

tabparam[compteur+1]:=Fields[compteur].AsVariant; RecValue:=CommUpdate(TabParam,0); if recvalue=0 then showmessage('pas de correspondance'); Next; end;

Le code est certes un petit peu plus long, mais c'est loin d'être insurmontable. Avec ce type de fonction je remplace complètement le moteur de curseur par une action par des commandes. Qui plus est, je ne modifie aucune des valeurs de mon Dataset.

Seulement cette fonction ne va pas toujours fonctionner, pour les mêmes raisons que leurs homologues ADO.NET. En effet, que va-t-il se passer si une des valeurs est NULL.

'UPDATE authors SET [year born]=:param1 WHERE [Au_ID]=:param2 AND [Author]=:param3 AND [Year Born]=:param4'

Si j'envisage le cas du premier enregistrement de ma base je vais générer la commande suivante :

'UPDATE authors SET [year born]=3 WHERE [Au_ID]=1 AND [Author]= 'Jacobs, Russell' AND [Year Born]=NULL'

Là je vais avoir 0 comme valeur de retour car la notation [Year Born]=NULL ne permet pas d'identifier l'enregistrement. Donc je dois modifier ma fonction de la façon suivante :

function TForm1.CommUpdate(const ListeParam: array of variant; const GestParam: integer):integer;

var ParamRequete: variant; compteur: integer; begin MaCommand.CommandType:=cmdtext; if GestParam=0 then MaCommand.Execute(result,vararrayof(ListeParam)) else begin

MaCommand.CommandText:='UPDATE ' + ListeParam[0] + ' SET ';

ParamRequete:=VarArrayCreate([0,Trunc(length(ListeParam)/2)- 1],varVariant);

compteur:=1;

MaCommand.CommandText:=MaCommand.CommandText+ '[' + ListeParam[compteur]+ ']=:param' + IntToStr(compteur);

compteur:=compteur+1;

While compteur<=GestParam do begin

MaCommand.CommandText:=MaCommand.CommandText+ ' AND [' + ListeParam[compteur]+ ']=:param' + IntToStr(compteur);

compteur:=compteur+1; end;

MaCommand.CommandText:=MaCommand.CommandText+ ' WHERE ([' + ListeParam[compteur]+ ']=:param' + IntToStr(compteur) + ' OR [' + ListeParam[compteur]+ '] IS NULL)';

compteur:=compteur+1;

While compteur<=Trunc(length(ListeParam)/2) do begin

MaCommand.CommandText:=MaCommand.CommandText+ ' AND ([' + ListeParam[compteur]+ ']=:param' + IntToStr(compteur)+ ' OR [' + ListeParam[compteur]+ '] IS NULL)';

compteur:=compteur+1; end;

for compteur:= 0 to Trunc(length(ListeParam)/2)-1 do ParamRequete[compteur]:=listeparam[Trunc(length(ListeParam)/2)+1+com pteur]; MaCommand.Execute(result,ParamRequete) end; end;

Et voilà une fonction correctement écrite. Ce passage était sûrement un peu fastidieux, mais maintenant vous voyez exactement comment fonctionne le moteur de curseur et les commandes paramétrées, nous allons pouvoir rentrer dans des sujets un peu plus complexes.

Documents relatifs