Professional Documents
Culture Documents
TADOCOMMAND .............................................................................................................36 Proprits.....................................................................................................................36 Mthodes ......................................................................................................................39 OBJETS INCLUS: TBOOKMARK, TFIELD, TINDEXDEF ........................................................40 TBookmark ...................................................................................................................40 TField...........................................................................................................................40 TIndexDef.....................................................................................................................43 TADOQUERY ...................................................................................................................44 Proprits.....................................................................................................................44 Proprits dynamiques..................................................................................................51 Mthodes ......................................................................................................................54 Evnements...................................................................................................................60 TADOTABLE....................................................................................................................63 Proprits et mthodes spcifiques................................................................................63 TADODATASET ...............................................................................................................64 TADOSTOREDPROC .........................................................................................................64 EXEMPLES DE CODE .....................................................................................................65 NOMBRE D'ENREGISTREMENT ET POSITION .......................................................................65 COMPARAISON SQL VS RECORDSET ................................................................................66 RECHERCHE SUCCESSIVES OU DIRECTIONNELLES ..............................................................68 Les dfauts de la mthode Locate..................................................................................68 Gestion des signets .......................................................................................................69 Programmation intrinsque ..........................................................................................72 PROGRAMMATION ASYNCHRONE ......................................................................................74 Connection et command asynchrone .............................................................................75 Extractions bloquantes & non bloquantes .....................................................................76 Suivre l'extraction.........................................................................................................77 Gestion des modifications .............................................................................................77 RECORDSET PERSISTANT...................................................................................................78 SYNCHRONISATION...........................................................................................................79 GENERATION DE COMMANDES ..........................................................................................81 Les trois valeurs de TField............................................................................................81 Similaire au moteur de curseur.....................................................................................81 Commande paramtre.................................................................................................85 TRAITEMENT PAR LOT.......................................................................................................90 Gestion standard des erreurs ........................................................................................93 Actions correctives........................................................................................................95 Utiliser une transaction ................................................................................................95 ACCES CONCURRENTIEL ...................................................................................................96 Les procdures stockes................................................................................................97 Le verrouillage .............................................................................................................97 Transactions et exclusivit ............................................................................................98 CONCLUSION SUR ADO.................................................................................................98 ADOX: MICROSOFT ACTIVEX DATA OBJECTS EXTENSIONS .............................99 IMPORTER LA BIBLIOTHEQUE ADOX DANS DELPHI. .........................................................99 MODELE OBJET ...............................................................................................................100 RAPPELS ACCESS ............................................................................................................100 Scurit ......................................................................................................................100 Paramtrage JET........................................................................................................100 NOTIONS FONDAMENTALES ............................................................................................101 ADOX & Access .........................................................................................................101 Propritaire................................................................................................................101
ParentCatalog ............................................................................................................101 L'OBJET CATALOG ..........................................................................................................101 COLLECTIONS DE L'OBJET CATALOG...................................................................102 COLLECTION TABLES ......................................................................................................102 COLLECTION PROCEDURES .............................................................................................103 COLLECTION VIEWS .......................................................................................................103 COLLECTION GROUPS .....................................................................................................103 COLLECTION USERS........................................................................................................103 L'OBJET TABLE ...............................................................................................................103 Collection Properties..................................................................................................104 Collection Columns ....................................................................................................104 Objet Column .............................................................................................................105 COLLECTION INDEXES ....................................................................................................108 Objet Index.................................................................................................................108 COLLECTION KEYS .........................................................................................................109 Quelques notions ........................................................................................................109 Mthode Append.........................................................................................................110 Objet Key ...................................................................................................................110 Exemples ....................................................................................................................111 Conclusion sur les tables ............................................................................................113 L'OBJET PROCEDURE ......................................................................................................113 Cration d'un objet procdure.....................................................................................113 Modification d'un objet Procedure..............................................................................114 L'OBJET VIEW...............................................................................................................114 CONCLUSION SUR LES OBJETS VIEW & PROCEDURE ........................................................114 GESTION DES UTILISATEURS ...................................................................................114 CAS PARTICULIER D'ACCESS ...........................................................................................115 PROPRIETES ET DROITS ...................................................................................................115 Propritaire................................................................................................................115 Administrateur............................................................................................................115 Utilisateurs et groupes................................................................................................116 Hritage des objets .....................................................................................................116 OBJET GROUP .................................................................................................................116 SetPermissions............................................................................................................116 GetPermissions...........................................................................................................119 OBJET USER ...................................................................................................................120 ChangePassword ........................................................................................................120 GetPermissions & SetPermissions ..............................................................................120 Properties...................................................................................................................120 EXEMPLE ........................................................................................................................120 TECHNIQUES DE SECURISATION ......................................................................................121 Modification ...............................................................................................................122 Cration .....................................................................................................................122 Une autre solution : le DDL........................................................................................122 CONCLUSION SUR LA SECURITE.......................................................................................122 CONCLUSION.................................................................................................................122
INTRODUCTION
Dans cet article nous allons regarder comment utiliser les accs des sources de donnes en utilisant ADO (ActiveX Data Object) et ADOX (extension pour la structure et la scurit) avec Delphi 7. Dans un premier temps nous dcouvrirons les technologies ADO ainsi que les concepts fondamentaux qu'il convient de connatre afin de les utiliser correctement. Dans la suite, nous verrons comment Delphi encapsule ces objets et permet de les mettre en uvre en utilisant les composants ou le code, enfin nous regarderons quelques exemples afin de dcouvrir quelques astuces. Pour ceux qui ont dj lu d'autres articles, que j'ai crit sur ADO pour VB, vous allez naviguer en terrain nouveau. En effet, du fait de l'encapsulation Delphi, la manipulation ADO est grandement modifie, je dirais mme amliore pour de nombreux points. S'il n'est pas possible d'utiliser ADO sans connatre ses principes fondamentaux, il est tout aussi vain d'essayer de le manipuler comme en VB ou en C++.
PREAMBULE
Lorsqu'on est dans l'diteur Delphi, on est en droit de se demander quel peut bien tre l'intrt de connatre les mcanismes de fonctionnement d'ADO. Il est tout d'abord vident qu'il s'agit de l'attaque d'une base de donnes autre qu'Interbase. Dans le cas de base de donnes de type Access ou Sql-Server, la meilleure stratgie consiste utiliser les composants ADO intgrs dans Delphi depuis la version 6. Ils sont assez semblables aux contrles standards et on peut penser qu'il n'y a rien de plus connatre. Le petit exemple qui va suivre va vous montrer pourquoi un tel apprentissage est indispensable pour peu que l'on veuille faire autre chose que de la simple consultation. Imaginons une table de base de donnes contenant des doublons parfaits et n'ayant pas de contrainte d'unicit. Dans ma feuille, j'ajoute un composant TADOConnection dont je paramtre la proprit ConnectionString afin de me connecter la source de donnes, je cre un composant ADODataset (ou ADOTable) qui utilise cette connexion et au travers d'un composant DataSource, j'alimente un composant DBGrid. A l'excution, ma table apparat. Si je change une valeur de ma table dans une ligne qui n'est pas un doublon, pas de problme, la modification est rpercute dans la source de donnes. Par contre si cette ligne est un doublon, j'obtiens une erreur. Si je regarde mon composant ADODataset, il possde une proprit CursorLocation dont la valeur est clUseClient. En modifiant celle-ci la valeur clUseServer, mes modifications se font sans erreur quelle que soit la ligne modifie. Bien sur une telle table est aberrante, mais ADO comporte de nombreux piges que nous allons dcouvrir ensemble.
Modle objet
Le modle objet ADO est fondamentalement assez simple tel que nous le voyons dans le schma ci-dessous. Ce schma est volontairement incomplet. En effet dans le cadre de ce document nous n'tudierons pas les objets Record et Stream.
La connexion
Contrairement une ide reue, avec ADO, il y a toujours une connexion au moins lors de la cration des objets de donnes. Le fournisseur peut tre un fournisseur de donnes ou un fournisseur de service, la source de donnes peut tre distante ou locale, mais la connexion existe toujours. Elle peut tre soit explicite c'est dire dclare par votre code et/ou vos composants, soit implicite c'est dire cre par ADO. La connexion est une session unique d'accs une source de donnes et dans le cas d'application client/serveur elle reprsente aussi une connexion au rseau. Globalement une connexion attend toujours au moins deux lments, un fournisseur et une source de donnes. Le maintient de cette connexion active est souvent plus une question de ressource que de stratgie, comme nous le verrons dans l'tude du mode dconnect.
Le fournisseur
ADO en lui-mme n'est qu'une interface de haut niveau permettant d'utiliser OLE DB. La connexion doit donc imprativement dfinir le fournisseur utilis. Il appartient au dveloppeur de vrifier la prsence de ce fournisseur. N.B : Attention, certains empaquetages rcents du MDAC de Microsoft(2.6+) ne contiennent pas certains composants ou fournisseurs comme Microsoft Jet, le fournisseur Microsoft Jet OLE DB, le pilote Desktop Database Drivers ODBC ou le pilote Visual FoxPro ODBC. Pour pouvoir utiliser pleinement les fonctionnalits ADO, il convient d'installer aussi une version plus ancienne (habituellement la version 2.1). Le fournisseur peut tre dsign sous la forme d'un DSN (Data Source Name) ou sous son nom. Je n'utiliserai pas les DSN dans cet article attendu que je dconseille vivement leur utilisation. Dans la thorie ADO, il est conseill de dclarer le fournisseur le plus tt possible. En effet, vous avez remarqu que tous les objets ADO contiennent une collection "Properties". Celle-ci englobe ce que l'on appelle les proprits dynamiques de l'objet. En fait chaque objet ADO contient quelques proprits fixes, et de nombreuses proprits donnes par le fournisseur, ce qui sous-tend que ces proprits peuvent ne pas exister selon les fournisseurs. Elles ne sont donc pas accessibles tant que le fournisseur n'est pas dfini. Ces proprits peuvent parfois tre indispensables au bon fonctionnement du code.
La source de donnes
La connexion doit aussi contenir une rfrence la source de donnes. Il est vident que le fournisseur doit pouvoir manipuler cette source. Certaines sources peuvent tre accdes par plus d'un fournisseur. Nous verrons avec ADOX qu'il est possible de crer la source de donnes par le code.
Synchronisation
Une connexion ADO peut tre synchrone ou non. Les connexions ADO n'imposent jamais d'tre synchrone, mais le dveloppeur peut les forcer tre asynchrones. Je m'explique, une connexion ADO est soit demande comme asynchrone, soit le fournisseur dcide. Il faut donc toujours bien garder l'esprit qu'une connexion peut ne pas tre toujours synchrone, selon la charge du serveur par exemple. De manire gnrale, il est moins lourd pour le serveur de traiter des connexions asynchrones.
Erreurs
Lorsqu'une erreur ADO se produit, il y a ajout d'un objet "Error" la collection Errors de la connexion active de l'objet, si tant est que celle-ci existe encore. Une erreur rcuprable se produit galement. Une erreur classique consiste croire que la collection garde la trace de toutes les erreurs survenues lors de la session. En fait lorsqu'une erreur se produit, elle cre un ou plusieurs objets "Error" dans la connexion selon que l'erreur entrane d'autres erreurs en cascade ou non. Si une autre erreur survient par la suite, la collection est vide et le processus recommence. Certains passage de la programmation ADO comme la mise jour par lot de jeu d'enregistrement dconnect demande le traitement de ces erreurs (cf l'exemple Traitement par lot).
Transactions
Il s'agit l d'un vaste sujet, que nous allons survoler seulement pour l'instant. Pour plus de renseignement je vous conseille de lire : Les transactions par Henri Cesbron Lavau A quoi servent les transactions ? par frdric Brouard (SQLPro) L'utilisation des transactions est fortement recommande, car elles assurent une cohsion une suite de traitement nettement plus dur raliser avec le code client. D'autant plus qu'on peut imbriquer des transactions. Ceci permet de valider certaines modifications, tout en gardant la possibilit d'annuler l'ensemble la fin, l'inverse prsentant moins d'intrt. Dans certains cas, on peut remplacer les transactions par du code client, mais celui-ci doit tre gr comme une transaction. Comme vous l'avez lu dans l'article de Frdric, le point de difficult se situe autour de l'isolation. D'ailleurs, la grande difficult de la programmation des SGBD repose sur la gestion des accs concurrentiels. Il faut toujours suivre les rgles suivantes : v Tout risque de lecture sale est bannir v La transaction doit tre courte v Dans le doute, la transaction doit chouer. Notez enfin que certains fournisseurs ne grent pas tous les degrs d'isolation. Nous verrons dans les exemples, ce qu'il faut faire et ne pas faire
Mode d'ouverture
Lorsqu'on ouvre une connexion ADO, on lui donne un certain nombre de privilges d'accs la source. L'exclusivit dfinit une connexion qui doit tre unique sur la source. Une telle connexion ne peut s'ouvrir s'il y a dj une connexion sur la source, et aucune autre connexion ne pourra avoir lieu sur une source ayant une telle connexion. L'exclusivit tant trs pnalisante, la connexion possde une proprit Mode qui dfinit soit ses propres droits soit qui restreint les droits des autres connexions ; tant bien entendu qu'une connexion restrictive peut restreindre les droits d'une connexion dj ouverte. Il convient donc d'tre vigilant lors de la restriction des droits.
Positionnement (CursorLocation)
Voil le concept qui fait le plus souvent dfaut. C'est pourtant un point fondamental. La position du curseur dfinit si les donnes du recordset sont situes dans le Process du client, c'est dire votre application ou dans celui du serveur, c'est dire celui du fournisseur. De plus le moteur du curseur et la bibliothque de curseur seront donns par le fournisseur si la position est ct serveur, alors qu'ADO invoquera le moteur de curseur client (composant de service) si la position est du ct client. La diffrence est primordiale car si vous n'avez pas vous proccuper du fonctionnement interne du fournisseur, il est utile de connatre le fonctionnement du moteur de curseur client. Nous verrons dans la discussion sur les curseurs clients tout ce que cela peut avoir d'important. La bibliothque de curseur dfinit aussi quelles fonctionnalits sont accessibles comme nous le verrons un peu plus loin. Certaines proprits / mthodes ne fonctionnent que si le curseur est du ct client et d'autres que s'il est du ct serveur. Nous verrons plus loin quand choisir l'un ou l'autre et pourquoi.
Verrouillage (LockType)
Le verrouillage (accs concurrentiel) est un lment indispensable des SGBD MultiUtilisateurs. Le verrouillage consiste interdire la possibilit a deux utilisateurs (ou plus) de modifier le mme enregistrement en mme temps. Il y a globalement deux modes de verrouillage :
10
11
Statique (ctStatic)
Position Oui Dfilement Bidirectionnel Sensibilit Pas de mise jour des donnes modifies Copie de donnes. Les curseurs ct client sont toujours statiques.
Jeu de cl (ctKeyset)
Position Oui Dfilement Bidirectionnel Sensibilit Reflte les modifications de donnes mais ne permet pas de voir les enregistrements ajouts par d'autres utilisateurs Demande d'tre utiliser avec des tables indexes. Trs rapide car il ne charge pas les donnes mais juste les cls.
Dynamique (ctDynamic)
Position Oui Dfilement Bidirectionnel Sensibilit Reflte toutes les modifications de donnes ainsi que les enregistrements ajouts ou supprims par d'autres utilisateurs Le poids lourd des curseurs. N'est pas support par tous les fournisseurs.
Hritage
La cration d'un objet Recordset sous-entend toujours la cration d'un objet Connection et d'un objet Command. Soit on cre ces objets de faon explicite soit ils sont crs par ADO de faon implicite lors de l'ouverture du recordset. La cration implicite pose deux problmes : Ces objets cessent d'exister lors de la fermeture du Recordset et doivent tre rcrs chaque excution et on multiplie ainsi le nombre de connexion la base. On oublie de paramtrer correctement ces objets, voire on oublie qui plus est qu'on les crs, mais le recordset hritant de certaines de leurs proprits, on n'obtient pas ce que l'on dsire. Ces deux objets se retrouvent dans les proprits Connection et Command de l'objet DataSet. Cet hritage est important comme nous allons le voir dans la suite de cet article. Un jeu d'enregistrement dont la proprit Connection vaut Nil et utilisant un curseur client est un jeu d'enregistrement dconnect, un jeu d'enregistrement dont la proprit ActiveCommand vaut Nil est dit "Volatile".
12
Taille du cache
Rgler la taille du cache peut tre important pour l'optimisation des curseurs serveurs. En fait, lorsque vous demandez un jeu d'enregistrement au fournisseur de donnes, celui-ci n'est pas forcment compltement rapatri dans l'espace client. Vous pouvez dfinir le nombre d'enregistrements qui seront transmis chaque fois en rglant la proprit CacheSize de l'objet DataSet. Ce rglage ce fait en donnant le nombre d'enregistrements que le cache peut accepter dans la proprit CacheSize. S'il y a plus d'enregistrements retourns qu'il n'y a de place dans le cache, le fournisseur transmet uniquement la quantit d'enregistrements ncessaire pour remplir le cache. Lors de l'appel d'un enregistrement ne figurant pas dans le cache, il y a transfert d'un nouveau bloc d'enregistrements. Ce mode de fonctionnement implique toutefois un pige. Lors des rafrachissements priodiques de votre recordset (dynamique ou KeySet), les enregistrements du cache ne sont pas rafrachis. Il faut forcer celui-ci en appelant la mthode Resync. Il n'y a pas de rgles absolues pour dire qu'elle taille de cache il faut utiliser. Je n'ai pour ma part pas trouv d'autres mthodes que de faire des tests selon les applications.
13
statut de l'enregistrement en cours. Si celui-ci est diffrent du UnModified, la mthode Post est alors appele, le statut de l'enregistrement change de nouveau et le jeu d'enregistrements bascule dans le mode navigation. Vous voyez bien, que la validation des modifications peut tre induite. Nanmoins une programmation vnementielle peut permettre un contrle sur ce genre de phnomnes.
Mta-donnes
Le recordset donc besoin pour fonctionner de donnes autres que celles renvoys par la requte, a fortiori si vous allez modifier la base par l'intermdiaire de ce recordset. Ces mta-donnes se divisent en deux blocs, celles qui sont stockes dans les proprits statiques et dynamiques des objets Fields, et celles qui sont stockes dans la collection des proprits dynamiques de l'objet Recordset. Celles stockes dans les proprits statiques des objets Fields sont les mmes quelle que soit la position du curseur. Par contre, les proprits dynamiques de l'objet recordset, ainsi que la collection Properties de chaque objet Field, sont trs diffrentes selon la position du curseur. Si on compare un recordset obtenu avec un curseur ct client de son homologue ct serveur, on constate qu'il possde beaucoup plus de proprits dynamiques, alors que le second est le seul possder la srie des proprits "Jet OLE DB". Ceci vient de la diffrence fondamentale de comportement du curseur. Lorsqu'on invoque un curseur ct serveur, le fournisseur OLE DB prend en charge la gestion de ce curseur. Il valorise quelques proprits dynamiques qui lui sont propres, mais le recordset n'a pas besoin de stocker des informations de schma puisque c'est le fournisseur qui va agir sur la base de donnes. Il ne faut pas confondre ce fonctionnement avec l'appel Delphi des mta-donnes (GetTableNames, GetFieldNames, etc) qui est li un appel spcifique de l'objet Connection (quivalent l'appel de OpenSchema ADO).
Donnes (Fields)
Pour stocker les donnes de la requte, les recordset possdent une collection Fields. Chaque objet Field reprsente un champ invoqu par la requte SELECT ayant cre le recordset. Un objet Field comprend : La valeur de la donne de l'enregistrement en cours. En fait, la valeur apparat trois fois. La proprit Value contient la valeur existante dans votre Recordset, la proprit UnderlyingValue contient la valeur existante dans la source de donnes lors de la dernire synchronisation enfin la proprit OriginalValue contient la valeur de la base lors de la dernire mise jour. Nous allons voir plus loin tout ce que cela permet et induit. Les mta-donnes du champ (nom, type, prcision). Elles sont disponibles avant l'ouverture du recordset si la connexion est dj dfinie dans l'objet Recordset et si la proprit source contient une chane SQL valide, sinon on ne peut y accder qu'aprs l'ouverture du recordset. Les informations de schma. Elles se trouvent dans la collection Properties de l'objet Field. Elles existent toujours quel que soit le ct du curseur, mais il y en a plus du ct client. Attention, ces informations peuvent tre fausses si le moteur de curseur n'en a pas besoin, pour un recordset en lecture seule par exemple. Les objets Field ADO ne sont pas encapsuls dans Delphi. Il convient de ne pas confondre les champs ADO avec les objets champs fournis par Delphi.
Mcanismes de base
Nous avons maintenant le minimum requis pour comprendre ce qui se passe lors de la manipulation d'un objet Recordset. Je rappelle ici ce point fondamental, l'utilisation des proprits dynamiques des objets ADO demande toujours la dfinition du fournisseur. En clair, la proprit Provider doit tre dfinie pour utiliser les proprits dynamiques de l'objet Connection, et la proprit Connection doit tre valorise pour utiliser les collections Properties des objets Command et Recordset.
14
Nous allons regarder maintenant quelques oprations courantes sur un recordset pour voir ce qui se passe au niveau du moteur de curseur. Ces exemples seront principalement dans le cadre des curseurs clients, puisque le fonctionnement des recordset ct serveur est gr par le fournisseur de donnes. Dans le cadre de Delphi je ne vais utiliser dans cette partie que les composants TADOConnection, TADOQuery et TADOCommand.
15
16
Echec ou russite
Il est important de comprendre comment le curseur va ragir lors de l'chec ou de la russite d'une action. Le moteur de curseur (client ou serveur) utilise les mta-donnes stockes dans le recordset pour valider une opration sur un recordset. Ce contrle lieu en deux temps : q Lors de l'affectation d'une valeur un champ La valeur saisie doit respecter les proprits de l'objet Field concern. Vous aurez toujours un message d'erreur si tel n'est pas le cas, mais vous n'aurez pas le mme message selon la position du curseur. Si par exemple vous tentez d'affecter une valeur de type erron un champ, vous recevrez un message d'incompatibilit de type pour un curseur Serveur, et une erreur Automation pour un curseur client. q Lors de la demande de mise jour Indpendamment de la position lorsqu'il y a un chec, une erreur standard est leve et il y a ajout d'une erreur la connexion dfinie dans la proprit Connection de l'objet DataSet. La proprit Status de l'enregistrement concern prendra une valeur cense reflter la cause de l'chec. L encore, la pertinence de cette valeur va varier selon la position du curseur. Dans une opration ct serveur, si dans un accs avec verrouillage optimiste je cherche modifier une valeur d'un enregistrement qui vient d'tre modifie par un autre utilisateur, j'obtiendrais une erreur "signet invalide" puisque le numro de version ne sera plus le bon. Dans la mme opration ct client, il y aura une erreur "violation de concurrence optimiste". Le pourquoi de ces deux erreurs diffrentes pour une mme cause va nous amener comprendre comment le moteur de curseur du client construit ses actions. En cas de russite de la modification, l'opration est marque comme russie (dans la proprit Status) et il y a alors synchronisation.
Synchronisation
La synchronisation est l'opration qui permet de mettre jour le jeu d'enregistrement avec les donnes de la source de donnes sous-jacente. La synchronisation est normalement explicite, c'est dire demande par l'utilisateur l'aide de la mthode Resync, mais elle suit aussi une opration de modification des donnes, elle est alors implicite. v Synchronisation explicite Elle peut porter sur tout ou partie d'un recordset ou sur un champ. Il existe deux stratgies diffrentes selon que l'on dsire rafrachir l'ensemble du recordset ou juste les valeurs sousjacentes. Dans le premier cas, toutes les modifications en cours sont perdues, dans l'autre on peut anticiper la prsence de conflits. v Synchronisation de modification Elle est plus complexe apprhender. Elle suit les rgles suivantes : Elle porte uniquement sur les enregistrements modifis dans votre recordset Les champs sur lesquels la valeur a t modifie sont toujours synchroniss Sur un recordset ct serveur, la synchronisation qui suit une opration sur les donnes et toujours automatique et non paramtrable. Elle porte sur tous les champs de l'enregistrement modifi. Par contre du ct client il est possible de spcifier le mode de synchronisation l'aide de la proprit dynamique "Update Resync". N'oublier pas la rgle d'or, les champs modifis d'une opration russie mettent la nouvelle valeur de la base dans leur proprit OriginalValue.
17
Modification (Update)
Commenons par un exemple simple. with ADOConnection1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; Connected:=true; end; with ADOQuery1 do begin SQL.Add('SELECT * From Authors'); Connection:=ADOConnection1; CursorLocation:= clUseClient; LockType:= ltOptimistic; CursorType:= ctStatic; Active:=true; Locate('Author','Gane, Chris',[]); Edit; FieldByName('year born').Value:=1941; Post; end; Lors de l'appel de la mthode Update, le moteur de curseur va construire une requte SQL UPDATE pour appliquer la modification. Par dfaut le modle de la requte est de la forme UPDATE Table SET Champs modifis=recodset.fields(Champs modifis).Value WHERE champs cls= recodset.fields(Champs cls).OriginalValue AND champs modifis= recodset.fields(Champs modifis).OriginalValue AND ventuelle condition de jointure Donc dans l'exemple que j'ai pris, la requte transmise par le moteur de curseur au fournisseur OLE DB sera : UPDATE Author SET [year born] = 1941 WHERE Au_Id = 8139 AND [year born] = 1938 Comment le moteur de curseur fait-il pour construire cette requte ? Il va chercher pour chaque champ modifi la table correspondante, cette information tant toujours disponible dans la collection properties de l'objet Field correspondant, puis il va rcuprer dans les mta-donnes la cl primaire de la table. Avec ces informations, il va construire sa requte sachant que les valeurs utilises dans la clause WHERE sont toujours tires des proprits OriginalValue des objets Fields de l'enregistrement en cours.
18
Si d'aventure vous n'avez pas inclus le(s) champ(s) constituant la cl primaire, il est possible que le moteur ait quand mme rcupr cette information. Si telle n'est pas le cas ou si votre table ne possde pas de cl primaire, le moteur lancera une requte d'information pour trouver un ventuel index unique lui permettant d'identifier de manire certaine l'enregistrement courant. Si cela s'avre impossible, vous obtiendrez une erreur comme nous l'avons vu plus haut. Il faut donc toujours veiller lors de l'utilisation d'un curseur client donner les informations ncessaires au moteur de curseur si on dsire effectuer des modifications de donnes. Cette requte est ensuite transmise au fournisseur OLE DB. Celui-ci retourne en rponse au moteur de curseur le nombre d'enregistrements affects. Si celui ci est gal zro, il y a dclenchement d'une erreur, sinon l'opration est marque comme russie. Il y a alors synchronisation de l'enregistrement. Tout a l'air parfait mais il y a une faute de verrouillage. En effet, s'il y a eu modification ou verrouillage de l'enregistrement par un autre utilisateur, ce n'est pas stricto sensu par le changement de numro de version du verrouillage optimiste que la requte choue mais parce que les valeurs stockes dans les proprits OriginalValue ne permettent plus d'identifier l'enregistrement en cours. Or cela peut tre trs diffrent. Imaginons le scnario suivant, dans le temps entre la cration du recordset et l'appel de la mthode Update, un autre utilisateur a chang le nom en "Gane, Peter". Normalement, du fait du verrouillage, la modification de la date de naissance devrait chouer, pourtant il suffit de lire le code SQL correspondant pour voir qu'elle va russir, puisque le champ 'Author' n'apparat pas dans la clause WHERE. Pour viter ce problme ou pour contourner d'autres limitations, on peut paramtrer les rgles de construction de la requte action du moteur de curseur l'aide de la proprit dynamique "Update Criteria". Il y a aussi un danger si on fait l'amalgame entre ADO et SQL. Avec une requte SQL de modification je peux m'affranchir de la valeur originelle du champ. Par exemple la requte SQL suivante est valide : UPDATE Author SET [year born] = [year born] + 1 WHERE Au_Id = 8139 Mais du fait de la concurrence optimiste elle ne peut pas tre excute par le moteur de curseur ADO avec un code comme celui ci-dessous : Locate('Author','Gane, Chris',[]); Edit; FieldByName('year born').Value:=FieldByName('year born').Value+1; Post; Ce code ne fonctionnera que si la valeur du champ [year born] dans la base est la mme que celle prsente
Suppression (DELETE)
Ce sont ces requtes qui posent le moins de problmes avec ADO. Dans ce cas, la requte n'utilisera que le(s) champ(s) composant la cl primaire pour crire la requte Delete SQL. Locate('Author','Gane, Chris',[]); Delete; Sera transform par le moteur en : DELETE FROM Authors WHERE Au_Id = 8139 L pas de problme de critre ou de synchronisation. NB : Dans le cas de la base biblio, une erreur sera dclenche puisqu'il y aurait tentative de suppression en cascade
19
(NumeroAuto ou TimeStamp), vous obtiendrez alors une erreur systmatique. De mme, donnez des valeurs tous les champs ne pouvant tre NULL avant d'appeler la mthode Update. Prenons un exemple : var Recup:integer; begin with ADOConnection1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\biblio.mdb;'; Connected:=true; end; with ADOQuery1 do begin SQL.Add('SELECT * From Authors'); Connection:=ADOConnection1; CursorLocation:= clUseClient; LockType:= ltOptimistic; CursorType:=ctStatic; Active:=true; //recordset.Properties['Update Resync'].Value:=1; Insert; FieldByName('Author').Value:='RABILLOUD, Jean-Marc'; FieldByName('year born').Value:=1967; Post; Edit1.Text:=FieldByName('Au_Id').AsString; end; end; Comme vous le voyez, je n'entre pas de valeur pour le champ "Au_Id" puisqu'il s'agit d'un champ NumeroAuto. La requte action crite par le moteur de curseur sera : INSERT INTO Authors (Author, [year born]) VALUES ("RABILLOUD , Jean-Marc",1967) La requte ne contient pas de valeur pour le champ "Au_Id" celle-ci tant attribue par le SGBD. Jusque l pas de problme. Mais que va-t-il se passer si j'ai besoin de connatre cette valeur dans la suite de mon code. Il me suffit d'crire : Recup :=FieldByName('Au_Id').Value Bien que je n'aie pas spcifi le mode de mise jour(ligne en commentaire), la valeur par dfaut de synchronisation est "adResyncAutoIncrement" (1) et le code fonctionne. Attention, ce code ne fonctionnera pas avec une version d'ADO infrieure 2.1 ni avec un fournisseur tel que JET 3.51. Cela fonctionne car le moteur de curseur envoie des requtes complmentaires afin de mettre jour le nouvel enregistrement. Cela ne fonctionnera pas avec Jet 3.51 car il ne supporte pas la requte SELECT @@IDENTITY qui permet de connatre le nouveau numro attribu, et sans celui-ci il n'est pas possible pour le moteur de curseur d'identifier l'enregistrement nouvellement cr. Pour connatre les possibilits de paramtrage de la mise jour, allez lire le paragraphe Proprits dynamiques - "Update Resync" de l'objet TADOQuery.
SQL.Add('SELECT * FROM Publishers, Titles WHERE Publishers.PubID = Titles.PubID'); Connection:=ADOConnection1; CursorLocation:= clUseClient; LockType:= ltOptimistic; CursorType:=ctStatic; Active:=true; Locate('Title','Evaluating Practice',[]); Edit; FieldByName('Name').Value:='ALPHA BOOKS'; FieldByName('Title').Value:=FieldByName('Title').Value+'s'; Post; end; Pour rsumer, je recherche le livre dont le titre est 'Evaluating Practice' je veux changer son diteur, par un autre diteur existant dans la base et ajouter un 's' au titre du livre. Bien sur, vue comme cela, la technique l'air un peu branlante, mais c'est exactement ce que 95% des utilisateurs essayent de faire de manire transparente avec un Datagrid par exemple. Que va-t-il se passer si vous excutez ce code? Le moteur de curseur va crire autant de Requte action que de table concerne par les modifications dans notre cas : UPDATE Publishers SET Name = 'ALPHA BOOKS' WHERE PubId = 129 AND Name = 'ALLYN & BACON' et UPDATE Titles SET Title = 'Evaluating Practices' WHERE ISBN = '0-1329231-8-1' AND Title = 'Evaluating Practice' Comme vous le voyez, ce n'est pas ce que nous voulions obtenir puisque l'diteur 'ALLYN & BACON' a disparu de la base. La faute est norme puisqu'il faut modifier la cl trangre pour obtenir ce que nous voulons mais nous voyons que le moteur de curseur ne peut pas grer ce style de programmation. Dans ce cas pourtant, il existe des proprits dynamiques qui permettent de contourner le problme. En effet vous pouvez dfinir l'aide de la proprit "Unique Table", une table sur laquelle porteront les modifications dans une requte avec jointure, et l'aide de la proprit "Resync Command" un mode de synchronisation pour ces mmes requtes. Pour voir leur utilisation, lisez l'exemple "proprits dynamiques Resync Command" du prsent cours.
21
Synchrone Vs Asynchrone
Avec ADO, on peut travailler de manire synchrone ou asynchrone. La programmation asynchrone prsente videmment l'avantage de ne pas bloquer l'application en cas de travaux longs du fournisseur mais demande de grer une programmation vnementielle spcifique. En fait, certains vnements ADO se produiront quels que soient le mode choisi, d'autres ne seront utiliss qu'en mode asynchrone. On peut travailler avec des connexions asynchrones et/ou des commandes asynchrones. Pour travailler de manire asynchrone vous devez utiliser un objet Connection (pas de connexion implicite). Le risque avec le travail asynchrone rside dans la possibilit de faire des erreurs sur des actions dites "bloquantes".
Les transactions
La plupart des SGBDR grent le concept de transaction (on peut le vrifier en allant lire la valeur de Connection.Properties("Transaction DDL")). Une transaction doit toujours respecter les rgles suivantes : v Atomicit : Soit toutes les modifications russissent, soit toutes chouent v Cohrence : La transaction doit respecter l'ensemble des rgles d'intgrit v Isolation : Deux transactions ne peuvent avoir lieu en mme temps sur les mme donnes v Durabilit : Les effets d'une transaction sont dfinitifs. Par contre toute transaction interrompue est globalement annule. Avec ADO, les transactions s'utilisent sur l'objet Connection
22
v Environnement scuris v Pas de code complexe pour l'utiliser Par contre, il faut qu'elle soit prvue dans la base ce qui est loin d'tre toujours le cas. Avec ADO elle s'utilise avec l'objet Command.
ltOptimistic ltBatchOptimistic
Recordset Vs SQL
Beaucoup de dveloppeurs expriments prfrent utiliser des requtes actions et du code SQL ds qu'il s'agit de modifier des donnes dans la base (j'entends par modifier, l'ajout, la suppression et la mise jour), plutt que d'utiliser les mthodes correspondantes de l'objet Recordset. Nous avons vu pourquoi cette technique est fortement recommandable si vous matrisez un tant soi peu le SQL, car elle prsente le net avantage de savoir exactement ce qu'on demande au SGBD. Nanmoins si vous ne connaissez pas bien le SQL, cela peut reprsenter plus d'inconvnients que d'avantages. Attention, passer par des requtes Action n'est pas sans risque s'il y a plusieurs utilisateurs car on peut obtenir des violations de verrouillage.
Recordset Persistant
Avec ADO il est possible de crer des jeux d'enregistrements persistants. Ceci consiste stocker le jeu d'enregistrement sous la forme d'un fichier. ADO gre deux formats de fichier ADTG et XML. Si le fichier est amen rester sur le poste client je vous conseille d'utiliser le format ADTG (format propritaire), par contre si vous tes amen transfrer ce fichier ou pour des applications Web, utilisez plutt XML. Le fichier se manipule l'aide des mthodes LoadFromFile et SaveToFile.
Optimisation du code
C'est une question qui revient souvent sur les forums. Avant de chercher une quelconque optimisation de votre code ADO, gardez bien l'esprit que la premire chose qui doit tre optimise est votre source de donnes. Les index manquants, les cls mal construites et surtout une structure anarchique de vos tables engendreront une perte de temps qu'aucune optimisation ne sache compenser.
24
L'objet command
Cet objet sert envoyer des requtes SQL (DDL ou DML) vers le SGBD, ainsi qu' la manipulation des procdures stockes. Si cet objet est fondamental dans la programmation d'ADO, c'est srement le moins bien utilis, car le moins bien peru des objets ADO. Cela vient du fait qu'ADO prsente deux aspects trs diffrents l'un de l'autre. Accs une source de donnes "gnrique". Le SGBD cible n'est pas dfini. On ne peut ds lors pas utilis de SQL puisque rare sont les SGBD respectant strictement le standard SQL. C'est un cas de programmation assez rare car il demande une trs bonne connaissance d'ADO pour tre efficace. Dans un tel modle de programmation l'objet Command est peu ou pas utilisable puisque mme la gnration de procdure stocke dans le modle ADOX est gnralement inaccessible. Accs une source de donnes connue. C'est le cas le plus frquent. On utilise ADO pour attaquer un SGBD que l'on connat et dont en gnral on connat le SQL. L'objet Command sert alors de moyen de communication avec le SGBD, gnralement de votre application vers le SGBD, mais aussi dans l'autre sens dans le cas des procdures stockes. Nous verrons que Borland a profit de l'encapsulation pour sparer l'objet en deux ce qui augmente la lisibilit. Mme si vous ne le voyez pas, les jeux d'enregistrements encapsulent leurs propres objets Command que vous pouvez modifier indirectement sans tre oblig de re instancier l'objet.
Communication bidirectionnelle
Elle n'est normalement employe que pour les requtes ou procdures paramtres, stockes dans la source et donnant lieu un renvoi d'information. Nous verrons dans l'tude dtaille de l'objet Command que le retour d'information est diffrent selon qu'il s'agit de paramtre ou d'enregistrement.
Collection Parameters
Les utilisateurs d'autres SGBD qu'Access veilleront bien faire la diffrence entre procdure stocke et requte paramtre.
25
Quoique regroups dans la mme collection, il existe deux types de paramtres. Les paramtres d'entre, attendus par la procdure/requte pour pouvoir s'excuter, et les paramtres de sortie qui peuvent tre renvoys par une procdure. Il convient de faire attention avec ceux-ci, une connexion n'acceptant jamais plus de deux objets Command ayant des paramtres de sortie (le paramtre de retour n'ayant pas d'influence). Les paramtres de cette collection reprsentent les valeurs attendues ou renvoyes par des requtes/procdures stockes dans la source de donnes. Lors de la dfinition d'un paramtre, il faut rigoureusement suivre les rgles suivantes : Utiliser des noms explicites pour vos paramtres. Le paramtre doit toujours tre correctement typ La proprit Size doit toujours tre prcise si le paramtre est de type potentiellement variable (CHAR et VARCHAR) La proprit Direction doit toujours tre prcise
26
TADOConnection
Encapsule l'objet Connection ADO. La liste suivante n'est pas exhaustive.
Proprits
Attributes (int)
Permet de grer la conservation des annulations ou des validations des transactions de l'objet Connection. Constante Valeur Description Excute un abandon avec sauvegarde : si vous appelez la commande xaAbortRetaining 262144 RollbackTrans, une nouvelle transaction commence automatiquement. Certains fournisseurs ne prennent pas cette valeur en charge. Excute une validation avec sauvegarde : si vous appelez la commande xaCommitRetaining 131072 CommitTrans, une nouvelle transaction commence automatiquement. Certains fournisseurs ne prennent pas cette valeur en charge.
CommandTimeout (int)
Indique le temps d'attente avant que la commande appele par la mthode Execute ne dclenche une erreur rcuprable. Ne confondez pas cette proprit de l'objet Connection avec celle du mme nom de l'objet Command. Les objets Command attachs une connexion ne tiennent pas compte de la valeur de cette proprit.
Connected (Del)
Indique si la connexion est ouverte ou ferme.
ConnectionObject (Del)
Permet d'accder directement l'objet Connection intrinsque ADO encapsul dans le composant TADOConnection. L'exemple suivant va vous montrer la technique d'utilisation. begin with ADOConnection1 do begin Provider:= 'Microsoft.Jet.OLEDB.4.0;'; DefaultDatabase:='D:\User\jmarc\tutorial\ADOX\baseheb.mdb'; ConnectionObject.Properties['Jet OLEDB:System database'].Value:='D:\User\jmarc\tutorial\ADOX\system.mdw'; ConnectOptions := coAsyncConnect; Open('Admin','password'); end; end; Dans cet exemple je vais dfinir la vole le fichier de scurit utilis par Access. Pour cela je vais faire appel une proprit dynamique de l'objet Connection "'Jet OLEDB:System database".
27
Toutes les proprits dynamiques ne sont pas encapsules dans Delphi. Pour pouvoir les utiliser, je vais donc devoir atteindre l'objet Connection ADO Intrinsque.
ConnectionString (int)
Cette proprit permet de passer une chane contenant tous les paramtres ncessaires l'ouverture de la connexion plutt que de valoriser les proprits une une. C'est cette proprit que l'on utilise avec le composant visuel TADOConnection, mais je vous dconseille de l'utiliser dans le code pour des raisons videntes de lisibilit.
ConnectionTimeout (int)
Tel que son nom l'indique. Attention, si CommandTimeout lve une exception, il n'en est pas de mme systmatiquement avec cette proprit. Selon les fournisseurs, il faut parfois aller tester la proprit Connected. De mme, si le fournisseur vous dconnecte pendant l'excution du code, vous ne rcuprerez pas d'erreurs tant que vous n'essayerez pas d'utiliser la connexion.
ConnectOptions (Del)
Remplace le paramtre option de la mthode Open ADO. Valeur Constante Description Ouvre la connexion en mode asynchrone. L'vnement coAsyncConnect 16 ConnectComplete peut tre utilis pour dterminer quand la connexion est disponible. coConnectUnspecified -1 (par dfaut) Ouvre une connexion en mode synchrone.* Je vous rappelle qu'en mode non spcifi, le fournisseur peut vous donner une connexion asynchrone.
CursorLocation (int)
Permet de dfinir la position du curseur des objets crs partir de cette connexion. Par dfaut il s'agit du cot client.
Errors (int)
Renvoie la collection des erreurs de la connexion. Voir l'exemple "Traitement par lot"
IsolationLevel (int)
Dfinit le degr d'isolation des transactions. Constante Valeur Description Le serveur utilise un niveau d'isolation diffrent de celui demand et ilUnspecified -1 qui n'a pu tre dtermin.. Valeur utilise par dfaut. Vous ne pouvez pas craser les ilChaos 16 changements en attente des transactions dont le niveau d'isolation est suprieur. IlBrowse ou partir d'une transaction, vous pouvez voir les modifications des 256 ilReadUncommitted autres transactions non encore engages. IlCursorStability ou A partir d'une transaction, vous pouvez afficher les modifications 4096 ilReadCommitted d'autres transactions uniquement lorsqu'elles ont t engages. partir d'une transaction, vous ne pouvez pas voir les modifications ilRepeatableRead 65536 effectues dans d'autres transactions, mais cette nouvelle requte peut renvoyer de nouveaux jeux d'enregistrements. IlSerializable ou 1048576Des transactions sont conduites en isolation d'autres transactions. ilIsolated
28
KeepConnection (Del)
Voil une proprit dont il convient de se mfier. Dans le principe, toute connexion n'tant pas lie un Dataset actif se ferme automatiquement. En mettant Vrai cette proprit, on demande le maintien ouvert de la connexion. C'est assez dangereux car : On a facilement tendance oublier de fermer la connexion Le serveur peut dcider malgr tout de la fermer. Il est souvent donc plus rentable cot client de laisser la connexion se fermer puis de la rouvrir pour envoyer les modifications.
Mode (int)
Prcise les autorisations de modification de donnes de la connexion. Constante Valeur Description Valeur utilise par dfaut. Indique que les autorisations n'ont pas encore cmUnknown 0 t dfinies ou ne peuvent pas tre dtermines. cmRead 1 Lecture seule. cmWrite 2 Ecriture seule. cmReadWrite 3 Lecture Ecriture. cmShareDenyRead 4 Ouverture interdite pour les utilisateurs autoriss en lecture seule. cmShareDenyWrite 8 Ouverture interdite pour les utilisateurs autoriss en criture seule. cmShareExclusive 12 Ouverture interdite aux autres utilisateurs. Empche les autres utilisateurs d'ouvrir des connexions avec n'importe cmShareDenyNone 16 quelle permission. L'utilisation de cette proprit est loin d'tre triviale comme nous allons le voir avec l'exemple ci-dessous. begin with ADOConnection1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; Attributes:= [xaCommitRetaining]; Mode :=cmReadWrite; end; with ADOConnection2 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; Mode :=cmShareDenyWrite; end; ADOConnection1.Connected:=True; ADOConnection2.Connected:=True; with ADOQuery1 do begin SQL.Add('SELECT * From Authors'); Connection:=ADOConnection1; CursorLocation:= clUseClient; LockType:= ltOptimistic; CursorType:=ctKeyset; Active:=true; Adoconnection1.BeginTrans; Filter:='author Like '+quotedstr('%Chris%') ; Filtered:=True; while not EOF do begin if FieldByName('year born').Value=Null then begin Edit; FieldByName('year born').Value:=1900; Post; 29
end; Next; end; Adoconnection1.CommitTrans; while not EOF do begin if FieldByName('year born').Value=1900 then begin Insert; FieldByName('year born').Value:=Null; Post; end; Next; end; Adoconnection1.CommitTrans; Application.MessageBox('Termin.','Look',0); end; end; Dans ce code vous allez avoir une erreur lors de l'ouverture de la connexion 2 puisqu'elle ne peut pas restreindre les droits de la connexion 1 dj ouverte. Imaginons maintenant que j'inverse l'ordre d'ouverture des connexions. L'erreur n'aura lieu qu' la premire rencontre de la mthode Post. En effet, le fait de se connecter aprs une connexion restrictive, restreint les droits de la nouvelle connexion sans avertissement. De plus, il n'y a jamais contrle du droit de lecture et/ou criture tant qu'ils ne sont pas rellement utiliss. L'erreur peut donc tre assez difficile cerner. Vous allez me dire, on s'en bat la rate avec un tentacule de poulpe pointill puisqu'une erreur doit normalement se produire. Certes, mais imaginons maintenant que je referme ma connexion2 avant l'ouverture du DataSet. Seule ma connexion 1 est ouverte et pourtant je vais de nouveau avoir une erreur lors de l'appel de la mthode Post. Cela vient du fait qu'une fois que les droits ont t restreints, ils ne sont pas restaurs lors de la fermeture de la connexion restrictive.
Provider (int)
Permet de dfinir le fournisseur. Trs utilis lorsqu'on veut dfinir les proprits dynamiques.
State (int)
Dtermine l'tat de la connexion. Constante Valeur Description StClosed 0 Valeur utilise par dfaut. Indique que l'objet est ferm. StOpen 1 Indique que l'objet est ouvert. StConnecting 2 Indique que l'objet est en train de se connecter StExecuting 4 Indique que l'objet est en train d'excuter une commande StFetching 8 Indique que les lignes de l'objet sont en cours d'extraction En lecture seule.
Proprits dynamiques
L'ensemble des proprits dynamiques ADO ne sont pas documentes. Il y en a beaucoup, mais je ne vais donner ici que les plus utilises. Prenez l'habitude de les accder au travers de ConnectionObject pour une meilleure lisibilit.
DataSource
Type chane. Chemin complet de la source de donnes
30
Extended Properties
Permet d'utiliser un fournisseur pour aller lire d'autres types de sources de donnes, typiquement Excel, texte format, etc with ADOConnection1 do begin Provider:='Microsoft.Jet.OLEDB.4.0;'; ConnectionObject.Properties['Data Source'].Value:='d:\configbase2.xls'; ConnectionObject.Properties['Extended Properties'].Value:='Excel 8.0'; Open; end;
31
Mthodes
BeginTrans, CommitTrans, RollBackTrans (int)
Mthode utilise pour la gestion des transactions. BeginTrans peut renvoyer un entier qui donne le niveau d'imbrication. Comme nous allons en voir dans de nombreux exemples, je ne m'tendrai pas dessus ici.
Cancel
Annule le dernier appel Asynchrone de la connexion. Ce n'est pas tellement que la mthode soit trs utile mais elle va me permettre de vous parler d'autre chose. Regardons le code suivant : var strUpdate : WideString; compteur:Integer; begin with ADOConnection1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; Connected:=True; strUpdate:='UPDATE Publishers SET City = ' + QuotedStr('Grenoble') + ' WHERE City = ' + QuotedStr('New York'); BeginTrans; Execute(strUpdate,cmdText,[eoAsyncExecute]); for compteur:=1 to 100 do if State = [stExecuting] then begin Cancel; RollBackTrans; end else CommitTrans; end;
32
end; Bien que techniquement juste, ce code va alatoirement dclencher des erreurs. Cela vient du fait que les transactions sur des connexions asynchrones doivent tre gres diffremment comme nous le verrons dans l'tude des vnements de cet objet. Si vous enlevez les mthodes de transaction, ce code fonctionne correctement.
Close (int)
Ferme la connexion. Utilisez plutt Connected:=False.
Execute (Del)
De la forme Connection.Execute(CommandText; CommandType; ExecuteOptions) Permet d'excuter une commande volatile partir de l'objet Connection (voir l'exemple au dessus). A ne surtout pas confondre avec la fonction Execute de l'objet Connection ADO sous-jacent. La commande est volatile car elle n'est pas rcuprable aprs excution. Le code Recup := Adoconnection1.Commands[0].CommandText; provoquera une erreur d'excution.
Execute (int)
De la forme Connection.Execute(CommandText; RecordsAffected; ExecuteOptions) Excute aussi une commande volatile avec possibilit de rcuprer le nombre d'enregistrements affects par la commande. Ces deux mthodes peuvent renvoyer un jeu d'enregistrement avec le curseur par dfaut.
Open (int)
De la forme Connection.Open(UserId ; Password) Permet l'ouverture de la connexion en fournissant les paramtres du login. Stricto sensu il n'y a pas de diffrence utiliser cette mthode plutt que la proprit Connected en mode synchrone, mais il faut utiliser Open en mode asynchrone
OpenSchema (int)
Permet de rcuprer des mta-donnes contenant les informations de schma de la source de donnes. Cette mthode est de la forme suivante: connection. OpenSchema (QueryType, Criteria, SchemaID, DataSet) O QueryType donne le type d'information rcuprer, Criteria dfinit les contraintes sur ces informations SchemaID n'est utilis que si QueryType est adSchemaProviderSpecific.
Pour avoir la liste complte des possibilits reportez-vous l'aide Delphi (Mthode OpenSchema (ADO)).
Comme nous le voyons, les informations renvoyes se trouvent dans un Dataset. Pour voir le principe de la mthode, observons le code suivant : var dsSchemaTable, dsSchemaChamp : TADOdataset; Fichier : TextFile; compteur : Integer; begin with ADOConnection1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; dsSchemaTable:=TADODataset.Create(Owner); adoconnection1.OpenSchema(siTables,EmptyParam, EmptyParam, dsSchemaTable); datasource1.DataSet:=dsSchemaTable; dsSchemaChamp:=TADODataset.Create(Owner); dsSchemaTable.First;
33
AssignFile(Fichier,'d:\user\confbase.txt'); Rewrite(Fichier); while not dsSchemaTable.Eof do begin writeln(Fichier,'Nom : ' + dsSchemaTable.FieldByName('TABLE_NAME').Value + ' type: ' + dsSchemaTable.FieldByName('TABLE_TYPE').Value + ' description : ' + dsSchemaTable.FieldByName('DESCRIPTION').Value) ; adoconnection1.OpenSchema(sicolumns,VarArrayOf([Unassigned, Unassigned, dsSchemaTable.FieldByName('TABLE_NAME').Value, Unassigned]), EmptyParam, dsSchemaChamp); for compteur:=1 to dsSchemaChamp.FieldCount-1 do write(Fichier,dsSchemaChamp.Fields.FieldByNumber(compteur).DisplayTe xt + chr(vk_tab)); dsSchemaTable.Next; end; closefile(Fichier); end; end; Ce code rcupre le schma des tables dans le dataset dsSchemaTable, et pour chaque table extrait le schma des colonnes. Observons la ligne suivante : adoconnection1.OpenSchema(sicolumns,VarArrayOf([Unassigned, Unassigned, dsSchemaTable.FieldByName('TABLE_NAME').Value, Unassigned]), EmptyParam, dsSchemaChamp); Si nous regardons l'aide nous trouvons dans le tableau : QueryType Criteria TABLE_CATALOG siColumns
TABLE_SCHEMA TABLE_NAME COLUMN_NAME
C'est donc pour obtenir la liste des champs d'une table que VarArrayOf([Unassigned, Unassigned, dsSchemaTable.FieldByName('TABLE_NAME').Value, Unassigned]). Les constantes QueryType n'tant pas toujours trs claires, et de manire gnrale pour comprendre les notions de schma et de catalogue http://sqlpro.developpez.com/SQL_AZ_7.html De nombreuses valeurs de QueryType pourraient dclencher une erreur. En effet, tout ce qui est li un utilisateur demanderait une connexion intgrant le fichier de scurit (.mdw) pour peu que celui-ci ne soit pas celui enregistr dans la cl de registre. Nous verrons ce point plus en dtail lors de l'tude de la scurit.
34
Evnements
A l'ouverture de la connexion, on peut dcider si celle-ci s'ouvre de faon asynchrone en valorisant le paramtre "Options" de la mthode Open adAsyncConnect. Les vnements de l'objet Connection sont : BeginTransComplete, CommitTransComplete, et RollbackTransComplete pour les transactions WillConnect, ConnectComplete et Disconnect pour l'ouverture et la fermeture InfoMessage pour la gestion des erreurs
35
TADOCommand
Encapsule une partie de l'objet ADO Command. Comme nous l'avons dj vu, l'objet Command ADO sert : Excuter des commandes SQL vers le SGBD Utiliser les procdures stockes Crer des jeux d'enregistrements (pas recommand). Dans Delphi, il existe deux composants pour cela, TADOCommand que nous allons voir ici et TADOStoredProc que nous verrons aprs.
Proprits
CommandObject (int)
Renvoie vers l'objet Command ADO intrinsque
CommandText (int)
Texte de la commande excuter
ExecuteOptions (int)
ExecuteOptionEnum Valeur Description Indique que la commande doit s'excuter en mode eoAsyncExecute 16 asynchrone Indique que les lignes restant aprs la quantit initiale spcifie dans la proprit "Initial Fetch Size" doivent tre rcupres en mode asynchrone. Si une ligne eoAsyncFetch 32 ncessaire n'a pas t rcupre, le chemin principal est bloqu jusqu' ce que la ligne demande devienne disponible. Indique que le chemin principal n'est jamais bloqu pendant la recherche. Si la ligne demande n'a pas t eoAsyncFetchNonBlocking 64 recherche, la ligne en cours se place automatiquement en fin de fichier. Cela n'a pas d'effet si le recordset est ouvert en mode adCmdTableDirect Une commande ou une procdure stocke qui ne eoExecuteNoRecords 128 renvoie rien. Si des lignes sont rcupres, elles ne sont pas prises en compte et rien n'est renvoy. Ce paramtre doit tre pass si l'on travaille en mode Asynchrone. Vous trouverez un exemple dans l'utilisation asynchrone de TADOQuery. Constantes
ParamCheck (Del)
Cette proprit permet un apport de souplesse par rapport la manipulation classique ADO. En effet, lors d'un changement de la proprit CommandText il y a gnration automatique des paramtres de la command si ParamCheck est vrai. Dans le cas ou vous utilisez d'autres paramtres il faut mettre cette proprit False. Cela videmment uniquement si votre commande contient des paramtres qui ne sont pas des paramtres ADO.
Parameters (int)
Permet d'accder la liste des paramtres ADO de la commande. Attention ne pas vous mlanger entre une requte paramtre (requte prsente dans le SGBD et retournant ou non un jeu d'enregistrement) et une procdure stocke (suite d'ordre SQL prsent dans le SGBD). Ici nous allons 36
voir les paramtres de requtes paramtres. Ces objets sont des Tparameter dont voici les proprits les plus utiles.
Attributes
Prcise si le paramtre accepte les valeurs signes (psSigned), binaires longues (psLong) ou NULL (psNullable).
Direction
Non utilis pour les requtes. Peut prendre les valeurs : Constante Valeur Description adParamUnknown 0 La direction du paramtre est inconnu. adParamInput 1 Valeur utilise par dfaut. Paramtre d'entre. adParamOutput 2 Paramtre de sortie. adParamInputOutput 3 Paramtre d'entre et de sortie. adParamReturnValue 4 Valeur de retour
Name
Dfinit le nom du paramtre. N'est pas obligatoire. En effet un paramtre non nomm apparat sous la forme '?' et est accessible par sa position ordinale
NumericScale
Donne la prcision (nombre de chiffres droite de la virgule) d'un paramtre numrique.
Size
Donne la taille en octet d'un paramtre dont le type est potentiellement de longueur variable (par exemple String). Ce paramtre est obligatoire pour les types de longueurs indtermins (String, Variant). Elle doit tre toujours valorise avant ou lors de l'ajout la collection.
Type
Comme son nom l'indique !
Value
De manire gnrale, valorisez cette proprit aprs l'ajout la collection. Et quelques mthodes
AppendChunk
Permet d'accder aux textes ou binaires longs.
Assign
Permet de dupliquer les proprits d'un paramtre dans un autre paramtre. Comme un exemple vaut mieux qu'un long discours, tudions le cas suivant. Soit la requte action suivante stocke dans Access sous le nom "ReqModif". PARAMETERS Annee integer; UPDATE Authors SET Authors.[Year Born] = 1972 WHERE Authors.[Year Born]=[Annee]; Pour l'utiliser en partant du code : var adoComModif: TADOCommand; begin with ADOConnection1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; Connected:=true; adoComModif:=TADOCommand.Create(owner); with adoComModif do begin
37
Connection:=adoconnection1; Parameters.Clear; CommandText:='ReqModif'; CommandType:=cmdStoredProc; Parameters.CreateParameter('Annee', ftInteger, pdinput, 4, 1941); Execute; end; end; end; Ce n'est pas bien compliqu. On pourrait aussi passer par la mthode AddParameter. Vous voyez bien que dans ce cas, je ne sais pas combien de modifications ont t effectues. Pour pouvoir rcuprer ce paramtre je dois excuter l'autre forme de la mthode Execute. var adoComModif: TADOCommand; nbChangement: Integer; begin with ADOConnection1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; Connected:=true; adoComModif:=TADOCommand.Create(owner); with adoComModif do begin Connection:=adoconnection1; CommandText:='ReqModif'; CommandType:=cmdStoredProc; Execute(nbChangement,VarArrayOf([1941])); end; end; end; Comme cette forme d'criture est plus simple, puisqu'on ne passe pas par la collection Parameters, certains dveloppeurs ont tendance la prfrer. Il faut quand mme voir qu'on travaille l directement sur l'objet Command ADO. Il y a une perte importante tant au niveau de la lisibilit que de la scurit utiliser ce style d'criture. Vous noterez aussi que bien qu'Access ne gre pas les procdures stockes, le CommandType est cmdStoredProc. Ceci permet ADO de savoir que le texte de la commande est dans le SGBD et qu'il attend des paramtres sans renvoyer d'enregistrements. Il est aussi possible de grer la commande et les paramtres par le code. var adoComModif: TADOCommand; paramAnnee: TParameter; begin with ADOConnection1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; Connected:=true; adoComModif:=TADOCommand.Create(owner); with adoComModif do begin Connection:=ADOConnection1; CommandText:='PARAMETERS Annee integer; UPDATE Authors SET Authors.[Year Born] = 1972 WHERE Authors.[Year Born]=[Annee];'; CommandType:=cmdText; Prepared:=True; ParamAnnee:= Parameters.AddParameter; with ParamAnnee do begin DataType:=ftInteger;
38
Name:='Annee'; Direction:=pdInput; end; BeginTrans; Parameters.ParamByName('Annee').Value:=1941; Execute; CommitTrans; Execute(VarArrayOf([1939])); end; end; end; J'utilise une autre technique d'criture afin de vous montrer le nombre de choix possibles. En toute rigueur ce code est trs lourd. On ne trouve jamais ce type de notation dans ADO mais uniquement dans ADOX, vous trouverez habituellement la notation : var adoComModif: TADOCommand; begin with ADOConnection1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; Connected:=true; adoComModif:=TADOCommand.Create(owner); with adoComModif do begin Connection:=ADOConnection1; CommandText:='PARAMETERS Annee integer; UPDATE Authors SET Authors.[Year Born] = 1972 WHERE Authors.[Year Born]=[Annee];'; CommandType:=cmdText; Prepared:=True; Execute(VarArrayOf([1941])); end; end; end; Pour les paramtres, nous en resterons l pour l'instant.
Prepared (Int)
Permet de prparer la commande avant de l'excuter. Il y a toujours une perte de temps lors de la prparation de la commande, mais celle-ci est beaucoup plus performante en cas d'appel multiple.
Mthodes
Assign (Del)
Duplique un objet Command
39
TBookmark
Il est possible de marquer certains enregistrements afin de pouvoir les localiser rapidement. On utilise pour cela des signets (BookMark). Ces signets sont en fait des pointeurs d'enregistrement. Une des applications standards consiste sauvegarder un tableau de pointeurs afin de pouvoir rcuprer un jeu d'enregistrement particulier. La valeur renvoye est une chane, il est ds lors conseill d'utiliser un objet liste pour construire son tableau de signets. Si un enregistrement marqu est supprim par un autre utilisateur, il est frquent que le signet dsigne alors un autre enregistrement sans dclencher d'erreur. Ceci est une source de Bug non ngligeable.
TField
Reprsente un objet champ gnrique. On n'utilise jamais directement TField mais plutt ses descendants. On peut se reprsenter un jeu d'enregistrement comme un tableau d'objet TField deux dimensions ayant : Une cohrence horizontale : Chaque ligne de champs reprsente un enregistrement, il n'est pas possible d'en dissocier les valeurs Une cohrence verticale : A une position ordinale donne, les champs ont tous les mmes attributs.
Concepts gnraux
Champ dynamique. Par dfaut les champs crs par un jeu d'enregistrements sont toujours des champs dynamiques. Cela veut dire que les champs reprsentent des champs de la source de donnes. Leur dure de vie est celle du jeu d'enregistrement. Un certain nombre de manipulation ne sont pas possibles sur les champs dynamiques. Leur intrt est par contre certain pour des applications flexibles. Champ persistant. Il est possible de dfinir ces champs la cration. On perd videmment en dynamique de programmation, mais on augmente fortement la souplesse des champs ainsi crs. Les champs renvoys par les objets ADO n'tant pas diffrents des autres je ne vais pas dtailler trop les concepts des objets champs, mais je vous recommande vivement de vous y intresser plus avant afin de profiter pleinement des possibilits offertes. Regardons maintenant la hirarchie des descendants TField.
40
TADTField Type Utilisateur TArrayField Type Tableau TDataSetField Dataset imbriqu TReferenceField pointeur
TAutoIncField Auto incrment TWordField Entier 16 bit TBCDField Dcimaux binaires court TFloatField Virgule flottante
TCurrencyField Montaire TFMTBCDField dcimaux binaire long TLargeintField Entier long TSmallIntField Entier court
TDateField Date TTimeField Heure TAggregateField Aggrgat persistant TBinaryField Binaire non typ
41
Proprits
Je ne vais dtailler ici que les proprits/mthodes utilises dans cet article
CurValue
Renvoie la valeur relle du champ lors des erreurs lies la mise jour diffre (optimiste).
Dataset
Identifie le dataset qui contient le champ
DefaultExpression
Spcifie une valeur par dfaut pour le champ
DisplayName
Fournit le nom du champ dans une chane
DisplayText
Fournit la valeur dans une chane. Le champ apparat format.
FieldKind
Permet d'identifier si le champ est de type calcul, agrg, etc..
Index
Renvoie ou dfinit la position ordinale du champ dans le jeu d'enregistrement. Sans effet sur la source de donnes.
IsIndexField
Permet de savoir si le champ fait partie d'un index.
IsNull
Permet de savoir s'il y a une valeur dans le champ
NewValue
Dtermine la valeur en attente dans le cache.
OldValue
Dfinit la valeur initiale du champ. En lecture seule. Les diverses proprits values seront tudies lors des mises jour par lot.
Required
Prcise si la saisie d'un champ est obligatoire
ValidChars
Permet de restreindre les caractres de saisie autoriss.
Value
Valeur du champ
Mthodes AssignValue
Permet d'affecter une valeur un champ tout en la transtypant.
42
Clear
Affecte la valeur NULL au champ.
IsValidChar
Dtermine si un caractre est valide pour ce champ.
Evnements OnChange
Se produit aprs l'envoie d'une valeur vers le cache.
TIndexDef
Cet objet reprsente un index sur une table ou un dataset. Attention, un index n'est pas une cl. Un index permet d'amliorer les recherches ou les tris, les cls sont des contraintes. Il ne faut pas non plus abuser de l'indexation des champs, sous peine d'alourdir la base ou les composants de donnes. Dans ADO on utilise les index sur des tables, en gnral obtenu par l'objet TADOTable. Seuls ceux-ci permettent d'utiliser la mthode de recherche Seek.
43
TADOQuery
Ce composant encapsule l'objet Recordset ADO. Son existence est trs intressante si vous avez l'habitude de manipuler l'objet Recordset ADO, sinon il vaut mieux utiliser TADOTable ou TADODataSet. Ce passage va tre assez technique et tout ce que vous y trouverez sera aussi vrai pour le composant TADODataset.
Proprits
Active (Del)
Ouvre le jeu d'enregistrements. Equivaut l'appel de la mthode Open.
BookMark (Int)
Permet de grer un signet sur l'enregistrement en cours.
CacheSize (Int)
Dfinit ou renvoie le nombre d'enregistrements placs dans le Cache. Il est souvent utile de faire un test de performance pour avoir une ide du rglage, tel que celui du code suivant. procedure TForm1.Button1Click(Sender: TObject); var temps, cmpt0,cmpt10,cmpt100 :Integer; adoRecordset:TADOQuery; adoConnect:TADOConnection; strAuthor:WideString; begin adoConnect:=TADOConnection.Create(Owner); with adoConnect do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\biblio.mdb;'; CursorLocation:=clUseServer; Connected:=True; end; adoRecordset:=TADOQuery.Create(Owner); with adoRecordset do begin Connection:=adoConnect; SQL.Add('SELECT * FROM Authors'); Active:=True; First; temps:=windows.GetTickCount; while not Eof do begin strAuthor:=Fields[0].Value; Next; end; cmpt0:=windows.GetTickCount-temps; CacheSize:=10; First; temps:=windows.GetTickCount; while not Eof do begin
44
strAuthor:=Fields[0].Value; Next; end; cmpt10:=windows.GetTickCount-temps; CacheSize:=100; First; temps:=windows.GetTickCount; while not Eof do begin strAuthor:=Fields[0].Value; Next; end; cmpt100:=windows.GetTickCount-temps; Close; end; adoConnect.Close; MessageDlg('Cache=1 : ' + IntToStr(cmpt0) + Chr(vk_return) + 'Cache=10 : ' + IntToStr(cmpt10) + Chr(vk_return) + 'Cache=100 : ' + IntToStr(cmpt100),mtInformation,[mbOK],0); end;
CanModify (Del)
Permet de savoir si le jeux d'enregistrement est modifiable (si le curseur n'est pas ReadOnly).
Connection (int)
Dfinit la connexion utilise
DataSource (Del)
Permet d'utiliser un composant DataSource comme fournisseur dynamique de paramtre votre recordset ADO ou du moins son objet Command de construction.
DefaultFields (Del)
Indique si les champs d'un jeu d'enregistrements sont dynamiques ou persistants.
EnableBCD (Del)
Force le stockage de valeur numrique dcimale comme Float ou comme Currency
Filter (Del)
Dfinit un filtre sur le jeu de donnes
FieldCount (Int)
Renvoie le nombre de champs prsents dans le jeu d'enregistrements.
45
FieldDefList (Del)
Renvoie la liste des dfinitions des champs du jeu d'enregistrements. S'utilise principalement dans une cration dynamique de composant
FieldValues (Del)
Renvoie un tableau Variant des valeurs de champs. Cette proprit est trs pratique mais je vais y mettre un bmol. Le tableau renvoy est un tableau de variant contenant les valeurs de tous les champs (donnes, agrgat, rfrence) o Un tableau de variant est une structure extrmement lourde. De plus la conversion des valeurs peut tre assez lourde. Si votre jeu d'enregistrement est volumineux, le cot en temps peut tre important. o La programmation des variants demande de l'habitude. Il convient donc d'tre assez vigilant. La programmation par FieldValues est par contre trs simple, voil un exemple classique adoRecordset.SQL.Add('SELECT [n Commande],[Date Commande] FROM Commandes'); adoRecordset.Active:=True; adoRecordset.Locate('N Commande',10273,[loCaseInsensitive]); VarRecup:=adoRecordset.FieldValues['Date Commande']; if vartype(varrecup)= varDate then begin adoRecordset.Edit; adoRecordset.FieldValues['Date Commande']:=IncDay(adoRecordset.FieldValues['Date Commande'],2); adoRecordset.Post; end; Pour mmoire la fonction IncDay augmente la date du nombre de jour spcifi en deuxime paramtre.
Filter (Del)
Les dveloppeurs ADO doivent faire attention ne pas confondre la proprit Filter ADO avec celle-ci. En effet, dans Delphi, deux proprits Filter et FilterGroup remplacent la proprit Filter ADO. Permet de filtrer les enregistrements d'un Dataset selon les critres spcifis. Le Dataset filtr se comporte comme un nouvel objet DataSet. En gnral cette proprit ne s'utilise pas sur les Dataset ct serveur car elle tend faire s'effondrer les performances. Par contre on l'utilise beaucoup lors des traitements par lot des curseurs clients. Le filtre doit tre de type filtre SQL, c'est dire qu'il s'utilise comme une clause WHERE sans le WHERE. procedure TForm1.FormActivate(Sender: TObject); var adoRecordset:TADOQuery; 46
begin adoRecordset:=TADOQuery.Create(Owner); with adoRecordset do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; CursorLocation:=clUseClient; CursorType:=ctStatic; LockType:=ltReadOnly; SQL.Add('SELECT * FROM Authors'); Active:=True; DataSource1.DataSet:=adoRecordset; end; end; procedure TForm1.Button1Click(Sender: TObject); begin DataSource1.DataSet.Filter :='[year born] <> null'; DataSource1.DataSet.Filtered:=True; end; N'oubliez pas que Filtered doit toujours tre faux avant une modification du filtre.
Filtered (Del)
Active ou dsactive le filtre
FilterGroup (Del)
Cette proprit n'est utilise que si le Dataset est en LockType ltBatchOptimistic. Elle sert filtrer les enregistrements selon leur statut de mise jour. Peut prendre une des valeurs prdfinies suivantes : Constante Valeur Description Enlve le Filtre ce qui rtabli le recordset d'origine. On obtient le fgNone 0 mme rsultat en affectant la valeur false la propritFiltered. Filtre les enregistrements en attente de mise jour (UpdateBatch ou fgPendingRecords 1 CancelBatch) Filtre les enregistrements affects par le dernier appel d'une mthode fgAffectedRecords 2 Delete, Resync, UpdateBatch ou CancelBatch. fgFetchedRecords 3 Filtre les enregistrements prsents dans le cache fgPredicate 4 Filtre utilis pour ne montrer que les lignes supprimes. Filtre les enregistrements n'ayant pas pu tre modifis lors de la fgConflictingRecords 5 dernire mise jour J'ai lu dans un article sur le Web que la combinaison de ces deux proprits diffrentes en Delphi donne un rsultat diffrent des filtres ADO intrinsque. Visiblement l'auteur n'a pas bien compris le fonctionnement des filtres ADO puisque c'est trs exactement identique. Regardons le code suivant. var adoRecordset:TADOQuery; begin adoRecordset:=TADOQuery.Create(Owner); with adoRecordset do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; CursorLocation:=clUseClient; CursorType:=ctStatic; LockType:=ltBatchOptimistic; SQL.Add('SELECT * FROM Authors'); 47
Active:=True; Filter:='[year born] <> null'; Filtered:=True; First; while not Eof do begin if FieldByName('Year born').Value<1945 then begin Edit; FieldByName('Year born').Value:=FieldByName('Year born').Value+1; Post; end; Next; end; FilterGroup :=fgPendingRecords; DataSource1.DataSet:=adoRecordset; end; end; Les donnes visibles sont bien celles des deux filtres ce qui est logique puisque le deuxime filtre affiche les valeurs modifies dj filtres par le premier filtre. Notez aussi que donner la valeur fgNone la proprit FilterGroup supprime aussi le filtre de la proprit Filter.
FilterOptions
Prcise si le filtre autorise le filtrage partiel et s'il est sensible la Casse (Minuscule/Majuscule).
Found (Del)
Cette proprit renvoie Vrai si la dernire recherche a abouti. Notez que le comportement lors d'une recherche Delphi est diffrent de la recherche intrinsque ADO.
MarshalOptions (int)
Cot client uniquement. Spcifie si tous les enregistrements ou seuls les enregistrement modifis sont renvoys vers le serveur.
MaxRecords (int)
Permet de limiter le nombre d'enregistrements renvoys par une requte. Pas de limitation quand la proprit vaut zro. Dans certains cas, cette proprit permet de protger le client contre le rapatriement d'un nombre trop important d'enregistrements.
ObjectView (Del)
Dfinit si la collection Fields se prsente sous forme hirarchise ou non.
48
RecNo (Del)
Equivalent de la proprit AbsolutePosition ADO. Renvoie la position de l'enregistrement actif si elle peut tre dtermine, sinon renvoie 0.
RecordCount (Int)
Renvoie le nombre d'enregistrement d'un Dataset lorsque celui ci peut tre dtermin, sinon renvoie -1. Pour cette proprit comme pour la prcdente, les curseurs "en avant seulement" ne peuvent dterminer la position en cours ni le nombre d'enregistrement. Elles renverront les valeurs que je vous ai signales et non une erreur comme vous pourrez parfois le lire. Cette erreur qui apparat parfois vient d'une erreur de curseur et non de la proprit RecordCount. Regardons le code suivant : begin adoRecordset:=TADOQuery.Create(Owner); with adoRecordset do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\biblio.mdb;'; CursorLocation:=clUseServer; CursorType:=ctOpenForwardOnly; LockType:=ltReadOnly; SQL.Add('SELECT * FROM Authors'); Active:=True; Locate('year born',1938,[loCaseInsensitive]); intRecup:= RecordCount; MessageDlg(IntToStr(intRecup),mtInformation,[mbOK],0); end; Ce code dclenchera une erreur sur la ligne intRecup:= RecordCount; ce qui fait que l'on pense que la proprit RecordCount renvoie une Erreur. En fait cela est du l'appel de Locate. Un curseur en avant seulement ne gre que Next comme mthode de dplacement.
Recordset (Int)
Dsigne le recordset sous-jacent de l'objet Query.
RecordsetState (Del)
Equivalent de la proprit State de l'objet Connection.
49
RecordStatus (Int)
Permet de connatre l'tat de la ligne en cours. Le traitement des tats est souvent indispensable lors des mises jours par lot. Peut prendre une ou plusieurs des valeurs suivantes : Constante Valeur Description rsOK 0 Mise jour de l'enregistrement russie rsNew 1 L'enregistrement est nouveau rsModified 2 L'enregistrement a t modifi rsDeleted 4 L'enregistrement a t supprim rsUnmodified 8 L'enregistrement n'a pas t modifi L'enregistrement n'a pas t sauvegard parce que son rsInvalid 16 signet n'est pas valide L'enregistrement n'a pas t sauvegard parce que cela RsMultipleChanges 64 aurait affect plusieurs enregistrements L'enregistrement n'a pas t sauvegard parce qu'il RsPendingChanges 128 renvoie une insertion en attente L'enregistrement n'a pas t sauvegard parce que RsCanceled 256 l'opration a t annule Le nouvel enregistrement n'a pas t sauvegard cause RsCantRelease 1024 du verrouillage d'enregistrements existants L'enregistrement n'a pas t sauvegard cause d'un RsConcurrencyViolation 2048 accs concurrentiel optimiste L'enregistrement n'a pas t sauvegard parce que RsIntegrityViolation 4096 l'utilisateur n'a pas respect les contraintes d'intgrit L'enregistrement n'a pas t sauvegard parce qu'il y RsMaxChangesExceeded 8192 avait trop de modifications en attente L'enregistrement n'a pas t sauvegard cause d'un RsObjectOpen 16384 conflit avec un objet de stockage ouvert L'enregistrement n'a pas t sauvegard parce que la RsOutOfMemory 32768 mmoire de l'ordinateur est insuffisante L'enregistrement n'a pas t sauvegard parce que RsPermissionDenied 65536 l'utilisateur n'a pas le niveau d'autorisation suffisant L'enregistrement n'a pas t sauvegard parce qu'il ne RsSchemaViolation 131072 respecte pas la structure de la base de donnes sousjacente L'enregistrement a dj t supprim de la source de RsDBDeleted 262144 donnes Une bonne manipulation de cette proprit est indispensable pour un travail hors transaction. Cette proprit n'est significative qu'en mode de traitement par lot (BatchOptimistic).
Sort (Int)
S'utilise avec une chane contenant le(s) nom(s) de(s) champ(s) suivi de ASC ou DESC, chaque champ tant spar par une virgule. Prfrez toujours un tri par le SQL qui est beaucoup plus performant sur les recordset ct serveur. Utilisez une chane vide pour revenir l'ordre de tri par dfaut. Cot client il y a cration d'un index temporaire si aucun index n'est prsent dans le jeu de donnes.
SQL (Del)
Similaire la proprit source ADO. Permet d'crire ou de modifier la chane SQL de l'objet Command de l'objet Recordset. Cette proprit est de types listes de chanes ce qui permet d'utiliser les proprits/mthodes de ce type pour manipuler le SQL. Cette commande peut tre paramtre. Toute
50
commande SQL valide est utilisable, cependant utiliser un Dataset pour n'excuter que des requtes actions est un non-sens.
State (Del)
Proche de la proprit EditMode ADO mais plus complet. Permet de connatre l'tat du recordset. Peut prendre de nombreuses valeurs mais on utilise principalement : Constante Description dsInactive L'ensemble de donnes est ferm dsEdit L'enregistrement actif peut tre modifi. L'enregistrement actif est un tampon nouvellement insr qui n'a pas dsInsert t transmis. Cet enregistrement peut tre modifi puis transmis ou abandonn Les donnes peuvent tre visualises, mais non modifies. C'est l'tat dsBrowse par dfaut d'un ensemble de donnes ouvert. Le DataSet est en cours d'ouverture mais n'a pas termin. Cet tat dsOpening arrive quand l'ensemble de donnes est ouvert pour une lecture asynchrone.
Proprits dynamiques
On utilise ces proprits en passant par la collection Properties de l'objet recordset renvoy par le DataSet. Elles dpendent principalement du fournisseur. Il en existe beaucoup, je vous en cite quelques-unes que nous utiliserons dans des exemples. Les proprits dynamiques se valorisent en gnrales aprs la dfinition du fournisseur pour l'objet, dans certains cas, aprs l'ouverture de l'objet. Certaines de ces proprits (Optimize par ex) ne sont plus utiles du fait de l'encapsulation Delphi.
51
SQL.Add('SELECT Publishers.PubID, Publishers.Name, Titles.Title, Titles.ISBN, Titles.PubID FROM Publishers , Titles WHERE Publishers.PubID = Titles.PubID'); Active:=True; Recordset.Properties['Unique Table'].Value:='Titles'; Recordset.Properties['Resync Command'].Value:= 'SELECT Publishers.PubID, Publishers.Name, Titles.Title, Titles.ISBN, Titles.PubID FROM Publishers, Titles WHERE Publishers.PubID = Titles.PubID AND Titles.ISBN=?'; Locate('Title','Evaluating Practice',[loCaseInsensitive]); Edit; FieldValues['Titles.PubId']:=192; FieldValues['Title']:=FieldValues['Title'] +'s'; Post; UpdateBatch(arCurrent); Recordset.Resync(1,2); DataSource1.DataSet:=adoRecordset; end; end; Nous voyons alors que la valeur apparaissant dans le TDBGrid est la bonne et que la modification dsire bien eu lieu dans la table. Dtaillons quelque peu. Vous noterez tout d'abord que je travaille directement sur l'objet recordset sous-jacent des fins de lisibilit et de cohrence. Je stipule tout d'abord la table sur laquelle vont porter les modifications. Puis, je passe la commande de synchronisation. Celle-ci stipule que lorsque je ferais appel la mthode Resync les enregistrements devront tre mis jour selon la cl primaire ISBN, ce qui revient dire que l'enregistrement synchronis ira re-extraire les donnes de la table 'Publishers' rpondant la condition de jointure de l'enregistrement considr. Enfin j'excute mes modifications puis ma synchronisation. Attention, la proprit dynamique 'Unique Table' ne fonctionne pas toujours bien dans Delphi selon les fournisseurs. Il reste prudent de faire attention de ne pas modifier les champs d'autres table avant d'avoir vrifi que le fournisseur la gre correctement. Clients
Update Criteria
Dfini comment le moteur de curseur va construire l'identification de l'enregistrement cible d'une modification, c'est dire les valeurs qui apparatront dans le WHERE de la requte UPDATE. Prend une des valeurs suivantes : Constante Valeur Description adCriteriaKey 0 Utilise uniquement les valeurs des champs cls. adCriteriaAllCols 1 Utilise les valeurs de tous les champs. (identique un verrou optimiste) adCriteriaUpdCols 2 Utilise les valeurs des champs modifis et des champs cls (dfaut) AdCriteriaTimeStamp 3 Utilise le champ TimeStamp au lieu des champs cl.
52
Attention l'utilisation de adCriteriaKey. Ceci implique que l'enregistrement sera identifi correctement, et que la valeur sera mise jour mme si elle a t modifie par quelqu'un d'autres. N'oubliez pas que les valeurs utilises par le moteur de curseur sont les valeurs stockes dans la proprit OriginalValue des objets Fields concerns. La gestion de cette proprit peut tre trs importante dans l'approche concurrentielle de votre application. Envisageons le cas suivant. J'excute deux programmes simultanment, leur code dans le FormActivate tant identique With ADOQuery1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\biblio1.mdb;'; CursorLocation:=clUseClient; CursorType:=ctStatic; LockType:=ltOptimistic; SQL.Add('SELECT * FROM Authors'); Active:=True; Recordset.Properties['Update Criteria'].Value:=2; Locate('Au_Id',8139,[loCaseInsensitive]); end; Dans ce cas la ligne 'Update Criteria' ne sert rien puisque 2 (adCriteriaUpdCols) est la valeur par dfaut. Chaque feuille possde respectivement un bouton dont le code est soit procedure TForm1.Button1Click(Sender: TObject); begin with ADOQuery1 do begin Edit; FieldValues['Author']:='Gane John'; post; end; end; Soit procedure TForm1.Button1Click(Sender: TObject); begin with ADOQuery1 do begin Edit; FieldValues['year born']:=1972; post; end; end; si je clique sur mes deux boutons, les deux modifications se droulent correctement dans la base de donnes. Si je procde au mme test en mettant la proprit dynamique 'Update Criteria' 1 j'obtiens une erreur de concurrence optimiste lors du clic sur le deuxime bouton. Ce comportement dans un mode d'accs concurrentiel devrait tre obligatoire puisqu'en toute rigueur l'enregistrement a t modifi. Mais il faut bien garder l'esprit que ce n'est pas le mode par dfaut du moteur de curseur client. Clients
53
Update Resync
Dfini comment le recordset est synchronis aprs un appel de la mthode Update ou UpdateBatch. N'oubliez pas que seuls les enregistrements modifis ou ajouts sont concerns par cette synchronisation. Prend une ou plusieurs des valeurs suivantes : Constante Valeur Description adResyncNone 0 Pas de synchronisation Tente de renvoyer les valeurs gnres adResyncAutoIncrement 1 automatiquement par le SGBD Synchronise tous les enregistrements n'ayant pas pu adResyncConflicts 2 tre mis jour lors du dernier appel de la mthode Update. adResyncUpdates 4 Synchronise tous les enregistrements mis jour adResyncInserts 8 Synchronise tous les enregistrements ajouts adResyncAll 15 Agit comme toutes les synchronisations prcdentes. Attention, le fait de paramtrer la proprit avec adResyncInserts ne suffira pas pour rcuprer la valeur des champs numrotation automatique. Dans ce cas vous devrez utiliser : Recordset.Properties['Update Resync']:= 1 + 8; Je reprends mon exemple prcdent. J'ajoute dans mes deux programmes un contrle DataSource et un DBGrid pour visualiser mes modifications. Je laisse le 'Update Criteria' par dfaut. Lors du clic sur le deuxime bouton il n'y a pas mise jour correcte de la deuxime grille puisque la modification apporte par l'autre programme n'apparat pas. Par contre si j'ajoute dans les procdures FormActivate la ligne Recordset.Properties['Update Resync'].Value:=4; J'aurais un affichage correct. Clients
Mthodes
Append (Del)
Similaire la premire syntaxe de la mthode AddNew ADO. Permet d'ajouter un enregistrement vide au Dataset. Cet enregistrement devient l'enregistrement actif.
AppendRecord (Del)
Identique la deuxime syntaxe de la mthode AddNew. Permet l'ajout d'un enregistrement rempli. Cet enregistrement devient l'enregistrement actif. Cette mthode est souvent plus efficace que la prcdente. Les valeurs donnes par le fournisseurs (valeur AutoInc par exemple) doivent tre mises "nil" lors de l'appel de la mthode. Par exemple with adoRecordset do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\biblio1.mdb;'; CursorLocation:=clUseServer; LockType:=ltPessimistic; SQL.Add('SELECT * FROM Authors'); Active:=True; adoRecordset.AppendRecord([nil,'Rabilloud',1967]); end;
54
BookmarkValid (Del)
Permet de vrifier si un signet est toujours valide.
ClearFields (Del)
Efface les valeurs des champs de l'enregistrement en cours, si celui-ci est en mode dition.
Clone (Int)
Duplique le jeu d'enregistrement. Le jeu ainsi obtenu est rigoureusement identique l'original. Il est possible de forcer le clone tre en lecture seule quel que soit le verrouillage de l'original. Une modification apporte l'original est rpercute sur tous les clones sauf si on a excut la mthode Requery sur l'original.
Close (Int)
Identique Active:=False
CompareBookmark (Del)
Compare deux signets et renvoie 0 s'ils sont gaux. Attention, renvoie aussi 0 si un des deux signets vaut nil.
Delete (Int)
Supprime l'enregistrement actif, et tente de se repositionner sur le suivant, sur le prcdent le cas chant. La mthode Delete ADO est assez sensible et peut engendrer facilement des dysfonctionnements. Pour viter ceux-ci il convient de prendre la prcaution suivante ; vider toujours le cache des modifications en attente mme si elles concernent un autre enregistrement.
DeleteRecords (Del)
Supprime un groupe d'enregistrement selon le critre spcifi. En mode par lot la suppression n'est effective qu'aprs l'appel de la mthode UpdateBatch. N'utilisez pas cette mthode qui ne fonctionne pas correctement avec MS-Access, et prfrez l'utilisation de l'ordre SQL quivalent.
Edit (Del)
Mais le jeu d'enregistrement en cours en cours en mode modification.
ExecSQL (Del)
Excute le code SQL de la proprit SQL de l'objet. Cette mthode est utilise lorsque la commande est une requte action. Une fois encore il n'est pas utile de se servir d'un TADOQuery pour faire des requtes actions.
FieldByName (Del)
Renvoie l'objet Field dont on passe le nom cette mthode.
55
FilterOnBookmarks (Del)
Filtre les enregistrements identifis par un signet. Attends en paramtre un tableau de signets. Cette fonctionnalit est propre ADO. Attention, utiliser cette mthode revient implicitement rinitialiser les proprits Filter et FilterGroup.
FindField (Del)
Similaire FieldByName, mais ne dclenche pas d'erreur rcuprable si le champ spcifi n'existe pas.
IsEmpty (Del)
Permet de dterminer si l'ensemble d'enregistrement est vide. Si vous avez l'habitude de programmer ADO, il vous faut maintenant utiliser cette mthode du fait du changement de dfinition des proprits BOF et EOF.
Locate (Del)
Mthode de recherche dans un jeu d'enregistrement implmente par Delphi. Cette mthode est beaucoup plus performante que la mthode Find ADO car elle utilise les index s'ils existent et qu'elle permet une recherche multi critres. Si un enregistrement est trouv, il devient l'enregistrement en cours. Toutefois, la diffrence de la mthode Find ADO, cette mthode ne permet pas de faire simplement des recherches successives avec le mme critre. On peut contourner ce problme (voir l'exemple "recherches successives")
Lookup (Del)
Cette mthode est aussi une mthode de recherche mais elle diffre de Locate dans son approche et dans sa finalit. Lookup ne modifie jamais l'enregistrement en cours, elle renvoie un tableau des valeurs dsires si la correspondance existe. En cas d'utilisation de champs calculs persistants, la mthode Lookup force le recalcul avant le renvoi des valeurs. Cette mthode peut tre trs intressante dans certains cas particulier mais elle contient un pige. Je vais crire un exemple de code particulirement mal que nous allons tudier ensemble.
56
procedure TForm1.FormActivate(Sender: TObject); var adoRecordset:TADOQuery; Signet:TBookmark; begin adoRecordset:=TADOQuery.Create(Owner); with adoRecordset do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; CursorLocation:=clUseClient; CursorType:=ctStatic; LockType:=ltOptimistic; SQL.Add('SELECT * FROM Authors'); Active:=True; Locate('Author','Vaughn, William',[]); Edit; FieldByName('year born').Value:=1901; Signet :=GetBookmark; Locate('Author','Vaughn, William R.',[]); if FieldByName('year born').Value =1947 then begin GotoBookmark(Signet); Cancel; end else begin GotoBookmark(Signet); Post; end; DataSource1.DataSet:=adoRecordset; end; end; end. Je suis sur que certains d'entre vous se demandent quel est l'intrt d'tudier un tel code, mais dites-vous bien que je voie de nombreux codes d'un style identique et que malheureusement j'en verrais d'autres. Dans le concept, il s'agit de valider une modification en fonction de la valeur d'un autre enregistrement. Quoique joyeusement dlirant ce code compile et s'excute sans erreur. Voyons les erreurs de ce code. Le curseur est l'envers. Le seul intrt d'un tel code tant de vrifier si la valeur du champ de dcision n'as pas chang du fait d'un autre utilisateur, on doit travailler ct serveur puisque les curseurs clients sont statiques et qu'un curseur statique ne reflte pas les modifications des autres utilisateurs. La modification sera toujours effectue si le deuxime enregistrement existe puisque la mthode Locate provoquera un changement de l'enregistrement en cours ce qui induit un appel implicite de la mthode Post. Quand bien mme le deuxime enregistrement n'existerait pas, il y aura tout de mme validation, car le simple Appel de la mthode Locate valide les modifications. Enfin l'appel de GotoBookmark posera les mmes problmes que Locate. Un code plus correct devrait tre de la forme with adoRecordset do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; CursorLocation:=clUseServer; CursorType:=ctKeySet; LockType:=ltOptimistic;
57
SQL.Add('SELECT * FROM Authors'); Active:=True; Locate('Author','Vaughn, William',[]); Edit; FieldByName('year born').Value:=1901; varTest:=adoRecordset.Lookup('Author','Vaughn, William R.','Year born'); if varTest =1947 then Cancel else Post; DataSource1.DataSet:=adoRecordset; end; Pourtant ce code ne fonctionnera pas mieux car bien que la mthode Lookup ne bouge pas l'enregistrement en cours en thorie, elle envoie tout de mme les modifications du tampon vers la source.
MoveBy (Del)
Dplace l'enregistrement en cours d'un nombre d'enregistrement prcis en paramtre. Renvoie la valeur du dplacement effectif. En effet, si la valeur demande excde le dplacement possible, le curseur se positionnera sur le premier (ou le dernier) enregistrement et enverra la valeur exacte du dplacement.
Open (Del)
Identique passer la proprit Active vrai.
58
Par exemple adoquery1.Recordset.Resync(3,2); synchronise l'ensemble des enregistrements en crasant les modifications en attente.
Fonctionnement
Un objet Recordset peut tre vu comme une collection d'enregistrements, chacun tant compos d'objet Field (champ). Lors de la cration du recordset, chaque champ possde trois proprits, OriginalValue, Value et UnderlyingValue qui reprsente la valeur du champ. Lorsque l'on fait des modifications on modifie la proprit Value, mais pas UnderlyingValue. Par comparaison des deux, le Recordset peut toujours "marquer" les enregistrements modifis. Si on procde une synchronisation des valeurs de la base, les proprits UnderlyingValue refltent les vrais valeurs de la base, mais les proprits Valeurs peuvent ne pas tre modifies. Le fait de synchroniser le recordset peut donc permettre de voir par avance si des valeurs ont t modifis par un autre utilisateur, en comparant les proprits UnderlyingValue et OriginalValue avant de dclencher une mise jour.
Supports
Cette mthode reste la meilleure amie du dveloppeur ADO. Elle permet de savoir si un jeu d'enregistrement donn va accepter une fonctionnalit spcifique. De la forme : boolean := Dataset.Supports( CursorOptions ) ou CursorOptions peut prendre les valeurs suivantes : Constante Valeur Description Vous pouvez utiliser la mthode AddNew pour ajouter de coAddNew 16778240 nouveaux enregistrements. Vous pouvez lire et dfinir les proprits AbsolutePosition et coApproxPosition 16384 AbsolutePage. Vous pouvez utiliser la proprit Bookmark pour accder des coBookmark 8192 enregistrements spcifiques. Vous pouvez utiliser la mthode Delete pour supprimer des coDelete 16779264 enregistrements. Vous pouvez utiliser la mthode Find pour localiser une ligne coFind 524288 dans un Recordset. Vous pouvez extraire plus d'enregistrements ou modifier la coHoldRecords 256 position d'extraction suivante sans valider toutes les modifications en attente. Vous pouvez utiliser la proprit Index pour donner un nom coIndex 8388608 un index. Vous pouvez utiliser les mthodes MoveFirst et MovePrevious, et les mthodes Move ou GetRows pour faire coMovePrevious 512 reculer la position de l'enregistrement en cours sans recourir des signets. coNotify 262144 Indique que le fournisseur gre les vnements du recordset Vous pouvez mettre jour le curseur avec les donnes visibles coResync 131072 dans la base de donnes sous-jacente, au moyen de la mthode Resync. Vous pouvez utiliser la mthode Seek pour localiser une ligne coSeek 4194304 dans un Recordset. Vous pouvez utiliser la mthode Update pour modifier les coUpdate 16809984 donnes existantes Vous pouvez utiliser la mise jour par lots (mthodes coUpdateBatch 65536 UpdateBatch et CancelBatch) pour transmettre des groupes de modifications au fournisseur. On utilise dans le code la mthode Supports pour savoir comment va ragir le Dataset et viter de faire de la gestion d'erreurs a posteriori.
59
UpdateBatch
Met jour toutes les modifications en attente (opration par lot). De la forme recordset.UpdateBatch AffectRecords O AffectRecords dfinit les enregistrements concerns (voir Resync).
Evnements
Les vnements de l'objet Recordset sont tous apparis sauf un. Ce qui veut dire qu'il se produit un vnement avant l'action concerne et un autre aprs. Ces vnements sont principalement utiliss en programmation asynchrone.
TEventStatus
Valeur que peut prendre le paramtre adStatus prsent dans presque tous les vnements. Constante Valeur Description Indique que l'opration qui a dclench l'vnement c'est esOK 1 passe avec succs Indique que l'opration qui a dclench l'vnement n'a pas esErrorsOccurred 2 pu se terminer correctement suite une erreur esCantDeny 3 Indique qu'il est impossible d'annuler l'opration en cours Une demande d'annulation t demande sur l'opration esCancel 4 en cours Empche de nouvelles notifications pendant le traitement esUnwantedEvent 5 d'un vnement
OnEndOfRecordset
EndOfRecordset(DataSet: TCustomADODataSet ;var MoreData: WordBool ; var EventStatus: TEventStatus) Se produit lorsque le recordset arrive en position EOF. O FMoreData dfini s'il reste des enregistrements aprs. Doit tre valoris "True" si pendant le traitement de l'vnement on ajoute des enregistrements EventStatus est l'tat du recordset. Vaut esOK si l'opration c'est bien passe et esCantDeny si l'opration qui a dclenche l'vnement ne peut tre annule. Si vous devez modifier le recordset pendant la procdure, ce paramtre doit tre mis esUnwantedEvent afin de supprimer les notifications non dsires. Dataset est la rfrence l'objet subissant l'vnement.
60
61
62
TADOTable
Les composants que nous allons voir maintenant sont tous similaires ceux dj vu sur de nombreux aspects. C'est donc sur leurs particularits que nous allons nous pencher. Ce composant permet de n'accder qu' une seule table de la source de donnes. Cet accs permet un accs dit "physique", c'est dire que les index de la table sont directement rcuprables.
TableDirect
Spcifie comment l'accs la table est rfrenc. Dans certains cas, mthodes Seek ADO par exemple, il est impratif que cette proprit soit valorise vrai.
TableName
Indique le nom de la table cible. N'est pas accessible en criture quand le composant est ouvert.
GetIndexNames
Renvoie une liste de tous les index disponibles pour une table.
Seek (int)
Permet de faire une recherche multi champs sur les tables indexes. Ne s'utilise que sur les curseurs serveurs. De la forme : ADOTable.Seek KeyValues, SeekOption O KeyValues est une suite de valeurs de type "Variant". Un index consiste en une ou plusieurs colonnes ou le tableau contient autant de valeurs qu'il y a de colonne, permettant ainsi la comparaison avec chaque colonne correspondante. SeekOption peut prendre les valeurs suivantes : Constante Valeur Description adSeekFirstEQ 1 Recherche la premire clef gale KeyValues adSeekLastEQ 2 Recherche la dernire clef gale KeyValues. Recherche soit une clef gale KeyValues ou juste aprs adSeekAfterEQ 4 l'emplacement o la correspondance serait intervenue. Recherche une clef juste aprs l'emplacement o une adSeekAfter 8 correspondance avec KeyValues est intervenue. Recherche soit une clef gale KeyValues o juste avant adSeekBeforeEQ 16 l'emplacement o la correspondance serait intervenue. Recherche une clef juste avant l'emplacement o une adSeekBefore 32 correspondance avec KeyValues est intervenue. Pour utiliser la mthode Seek, il faut que le recordset supporte les index et que la commande soit ouverte en adCmdTableDirect. Comme vous le verrez dans l'exemple le principe est simple, on ajoute un ou des index sur le(s) champ(s) cible de la recherche, et on passe le tableau des valeurs cherches la mthode Seek. Serveurs Pour Access, le fournisseur Jet 3.51 ne gre pas cette mthode Le code suivant recherche l'auteur ayant le code 54
63
with ADOTable1 ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; CursorLocation:=clUseServer; TableDirect:=True; TableName:='Authors'; Active:=True; IndexName:='PrimaryKey'; Seek(54,soFirstEq); end;
TADODataset
C'est sensiblement le mme objet que TADOQuery si ce n'est qu'il y a utilisation d'un objet Command en lieu et place du texte SQL.
TADOStoredProc
Il s'agit d'un Dataset un peu particulier. Il possde une proprit ProcdureName qui permet de dfinir le nom de la procdure utilise. Il faut ensuite utiliser la collection Parameters comme pour l'objet Command. Gardons quelques points importants l'esprit. Cet objet sert utiliser les procdures stockes paramtres renvoyant un jeu d'enregistrements, sinon prfrer l'utilisation de l'objet Command. Il existe trois types de paramtres o Les paramtres d'entres sont ceux que la procdure attend pour s'excuter correctement o Les paramtres de sorties sont ceux que la procdure vous fournis en rponse. o Le paramtre de retour est un paramtre particulier que certaines procdures renvoient. Il est important de ne pas confondre paramtre de sortie et paramtre retour, donc de bien connatre la procdure appele.
64
Exemples de code
Dans cette partie nous allons regarder quelques exemples de code un peu plus complexe. Comme dans la partie prcdente, il m'arrive de passer directement la connexion dans le Dataset. Cela ne veut pas dire qu'il s'agit d'un exemple suivre, c'est juste pour diminuer la complexit du code la lecture.
65
procedure TForm1.DBLookupComboBox1Click(Sender: TObject); begin ProgressBar1.Position:=adoRecordset.RecNo; end; procedure TForm1.FormCreate(Sender: TObject); begin adoRecordset:=TADOQuery.Create(Owner); with adoRecordset do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\biblio.mdb;'; CursorLocation:=clUseServer; CursorType:=ctKeySet; LockType:=ltReadOnly; SQL.Add('SELECT * FROM Authors'); Active:=True; ProgressBar1.Min:=1; ProgressBar1.Max:=RecordCount; end; Datasource1.DataSet:=adoRecordset; with DBLookupComboBox1 ListSource:=Datasource1; KeyField:='Au_Id'; ListField:='Author'; end; end; end. Dans le "FormCreate", je cre mon dataset. J'affecte ensuite mon dataset comme source de donnes du DataSource, et je prcise quel champ servira pour le remplissage de la liste ainsi que le champ servant de cl. Tout se passe donc dans l'vnement Click du TDBLookupComboBox. Un click sur une valeur dans la liste a pour effet de faire bouger l'enregistrement courant vers l'enregistrement dont la valeur est slectionne. Ds lors la rcupration de la position est visible dans la ProgressBar.
66
begin with ADOQuery1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; CursorLocation:=clUseServer; CursorType:=ctKeySet; LockType:=ltReadOnly; SQL.Clear; SQL.Add('SELECT * From Authors WHERE Author LIKE ' + QuotedStr('S*') + ' ORDER BY Author, [Year Born] DESC'); tmpStart:=windows.GetTickCount; Active:=True; tmpEnd:=windows.GetTickCount; MessageDlg(IntToStr(tmpEnd-tmpStart),mtInformation,[mbOK],0); Close; SQL.Clear; SQL.Add('SELECT * From Authors WHERE Author LIKE ' + QuotedStr('S*') + ' AND NOT [Year Born] IS NULL ORDER BY Author, [Year Born] DESC'); tmpStart:=windows.GetTickCount; Active:=True; tmpEnd:=windows.GetTickCount; MessageDlg(IntToStr(tmpEnd-tmpStart),mtInformation,[mbOK],0); Close; end; end; Tel que nous le voyons, je procde deux extractions successives. Dans mon second test, je ne vais travailler qu'avec les fonctionnalits du recordset. Je vais donc extraire l'ensemble de la table, puis travailler sur le jeu d'enregistrement. La proprit Sort demande un curseur ct client. De ce fait le curseur sera statique. Le code utilis sera le suivant : procedure TForm1.btnRecordClick(Sender: TObject); var tmpStart, tmpEnd:integer; begin with ADOQuery1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; CursorLocation:=clUseClient; CursorType:=ctStatic; LockType:=ltReadOnly; SQL.Clear; SQL.Add('SELECT * From Authors'); tmpStart:=windows.GetTickCount; Active:=True; tmpEnd:=windows.GetTickCount; Filter:='Author Like ' + QuotedStr('S*'); Filtered:=True; Sort:='Author ASC, [Year Born] DESC'; MessageDlg(IntToStr(tmpEnd-tmpStart),mtInformation,[mbOK],0); tmpStart:=windows.GetTickCount; Filter:='Author Like ' + QuotedStr('S*')+ 'AND [Year Born] <> NULL'; Filtered:=True; tmpEnd:=windows.GetTickCount; MessageDlg(IntToStr(tmpEnd-tmpStart),mtInformation,[mbOK],0); Close;
67
end; end; Chaque programme affichera donc deux temps, le temps qu'il lui faut pour obtenir un jeu d'enregistrement tri contenant tous les auteurs dont le nom commence par "S" et temps qu'il met pour obtenir le mme jeu dont le champ "anne de naissance est rempli. Les temps ne seront jamais parfaitement reproductible car de nombreux paramtre peuvent jouer, mais on constate que le code SQL va plus vite que le filtrage et le tri du recordset (premier temps). Ceci est du au fait que le curseur client rapatrie l'ensemble des valeurs dans son cache, puis applique le filtre et tri les enregistrements. Il est noter que le tri par la mthode sort du recordset est trs pnalisant. Par contre, pour le deuxime temps, le second code est toujours plus rapide (deux trois fois plus). Cela vient du fait qu'il n'est plus ncessaire de r extraire les donnes, celles-ci tant prsente dans le cache du client ce qui permet une excution trs rapide. Tout cela montre bien qu'il ne faut pas a priori rejeter les fonctionnalits recordset comme on le lit trop souvent. Ce qui pnalise beaucoup les curseurs client, c'est la ncessite de passer dans le cache l'ensemble des valeurs (Marshaling) mais une fois l'opration faite, le travail devient trs rapide. Une fois encore, tout est question de besoin.
68
69
Label2: TLabel; DBEdit2: TDBEdit; Label3: TLabel; DBEdit3: TDBEdit; btnMvFirst: TButton; btnMvNext: TButton; btnMvLast: TButton; btnMvPrev: TButton; ADOQuery1: TADOQuery; procedure FormCreate(Sender: TObject); procedure btnMvFirstClick(Sender: TObject); procedure btnMvLastClick(Sender: TObject); procedure btnMvPrevClick(Sender: TObject); procedure btnMvNextClick(Sender: TObject); private { Dclarations prives } public { Dclarations publiques } procedure ConstruitSignet(ADOQuery1:TADOQuery); end; var Form1: TForm1; TabSignet:Array of TVarRec; CurPos : Integer; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin with ADOQuery1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\biblio.mdb;'; CursorLocation:=clUseClient; CursorType:=ctStatic; LockType:=ltReadOnly; SQL.Add('SELECT * FROM Authors'); Active:=True; Filter:='Author like '+QuotedStr('s*'); Filtered:=True; end; ConstruitSignet(ADOQuery1); datasource1.DataSet:=adoquery1; end; procedure TForm1.ConstruitSignet(ADOQuery1:TADOQuery); var intElem:integer; begin with ADOQuery1 do begin First; intElem:=0; while not EOF do begin
70
if strpos(PChar(FieldByName('Author').AsString),PChar('John'))<>nil then begin SetLength(TabSignet,intElem+1); Tabsignet[intElem].VType:=vtPointer; Tabsignet[intElem].VPointer:=GetBookmark; intElem:=intElem+1; end; Next; end; end; end; procedure TForm1.btnMvFirstClick(Sender: TObject); begin CurPos:=low(TabSignet); ADOQuery1.GotoBookmark(TabSignet[CurPos].VPointer); end; procedure TForm1.btnMvLastClick(Sender: TObject); begin CurPos:=high(TabSignet); ADOQuery1.GotoBookmark(TabSignet[CurPos].VPointer); end; procedure TForm1.btnMvPrevClick(Sender: TObject); begin if CurPos>low(TabSignet)Then begin CurPos:=CurPos-1; ADOQuery1.GotoBookmark(TabSignet[CurPos].VPointer); End; end; procedure TForm1.btnMvNextClick(Sender: TObject); begin if CurPos<high(TabSignet)Then begin CurPos:=CurPos+1; ADOQuery1.GotoBookmark(TabSignet[CurPos].VPointer); End; end; end. Ce code rsout correctement le problme pose. Une petite modification de la procdure "ConstruitSignet" permettrai de laisser l'utilisateur saisir la chane de recherche. Il reste tout de mme deux problmes consquents. Le cot en terme de temps d'excution est trs important. Chaque modification du filtrage ou de l'ordre de tri devra engendrer une reconstruction du tableau de signet. Comme celui-ci est bas sur le parcourt l'ensemble du Dataset, la mthode est extrmement lourde. Ce prix peut tre toutefois acceptable. Cependant la fonction contient un pige majeur. En mode Multi-Utilisateurs, Le tableau de signet peut tre faux du fait des modifications apportes par d'autres utilisateurs (ajout, modification ou suppression). Cot serveur, il faudrait coupler la fonction a la synchronisation. Ds lors, le temps d'excution du code est liminatoire sauf sur des jeux d'enregistrements trs petits. Cot client comme le curseur est forcment statique, c'est le programme qui gre la synchronisation il suffira donc d'intgrer la procdure au code de synchronisation.
71
Cette reconstruction du tableau de signet est indispensable. En effet plusieurs types d'erreurs peuvent se produire lors de l'appel d'un signet faisant rfrence un enregistrement supprim.
Programmation intrinsque
Une autre mthode malheureusement mconnue consiste programmer directement le Recordset ADO sous-jacent avec ses mthodes propres, tant bien entendu que cela n'est utile que pour des mthodes n'tant pas implmentes dans Delphi. Regardons le code suivant. procedure TForm1.FormCreate(Sender: TObject); begin with ADOQuery1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\biblio.mdb;'; CursorLocation:=clUseClient; CursorType:=ctStatic; LockType:=ltReadOnly; SQL.Add('SELECT * FROM Authors'); Active:=True; Filter:='Author like '+QuotedStr('s*'); Filtered:=True; end; datasource1.DataSet:=adoquery1; end; procedure TForm1.btnMvFirstClick(Sender: TObject); begin ADOQuery1.Recordset.Find('Author like '+QuotedStr('*John*'),0,1,1); end; procedure TForm1.btnMvLastClick(Sender: TObject); var SearchBack:integer; begin SearchBack:=-1; ADOQuery1.Recordset.MoveLast; ADOQuery1.Recordset.Find('Author like '+QuotedStr('*John*'),0,SearchBack,EmptyParam); end; procedure TForm1.btnMvPrevClick(Sender: TObject); var SearchBack:integer; begin SearchBack:=-1; ADOQuery1.Recordset.Find('Author like '+QuotedStr('*John*'),1,SearchBack,EmptyParam); end; procedure TForm1.btnMvNextClick(Sender: TObject); begin ADOQuery1.Recordset.Find('Author like '+QuotedStr('*John*'),1,1,EmptyParam); end; end. Il est beaucoup plus simple que le prcdent. Vous voyez que j'utilise la mthode Find ADO dont la dfinition est la suivante.
72
73
ADOQuery1.Recordset.MoveLast; ADOQuery1.Recordset.Find('Author like '+QuotedStr('*John*'),0,SearchBack,EmptyParam); if ADOQuery1.Recordset.BOF then ADOQuery1.Recordset.Bookmark:=PosLocal else ADOQuery1.MoveBy(Integer(adoquery1.Recordset.AbsolutePosition)ADOQuery1.RecNo); end; procedure TForm1.btnMvNextClick(Sender: TObject); var PosLocal:OLEVariant; begin PosLocal:=ADOQuery1.Recordset.Bookmark; ADOQuery1.Recordset.Find('Author like '+QuotedStr('*John*'),1,1,EmptyParam); if ADOQuery1.Recordset.EOF then ADOQuery1.Recordset.Bookmark:=PosLocal else ADOQuery1.MoveBy(Integer(adoquery1.Recordset.AbsolutePosition)ADOQuery1.RecNo); end; procedure TForm1.btnMvPrevClick(Sender: TObject); var SearchBack:integer; PosLocal:OLEVariant; begin SearchBack:=-1; PosLocal:=ADOQuery1.Recordset.Bookmark; ADOQuery1.Recordset.Find('Author like '+QuotedStr('*John*'),1,SearchBack,EmptyParam); if ADOQuery1.Recordset.BOF then ADOQuery1.Recordset.Bookmark:=PosLocal else ADOQuery1.MoveBy(Integer(adoquery1.Recordset.AbsolutePosition)ADOQuery1.RecNo); end; Quelques remarques encore sur ce code. v Pour la recherche arrire j'utilise la variable SearchBack car le passage direct de la valeur 1 la mthode Find dclenche une erreur de compilation. v J'utilise la proprit ADO Bookmark pour marquer la position plutt que la mthode Delphi GetBookmark. En effet, lorsqu'on travaille sur le recordset, il convient de n'utiliser que des proprits / mthodes de celui-ci. La programmation intrinsque pourrait encore faire l'objet de nombreux exemples. Je ne saurais trop vous recommander de n'utiliser celle-ci que lorsque l'objet Dataset ne peut pas rpondre vos attentes, car elle demande une bonne connaissance de la programmation ADO et elle peut engendrer de grave dysfonctionnement.
Programmation asynchrone
Celle-ci n'est pas plus complexe que la programmation vnementielle des contrles. Il faut toutefois garder l'esprit que le changement de l'enregistrement courant valide les modifications. Comme un simple Find fait changer l'enregistrement courant, on devine facilement l'intrt de la programmation asynchrone pour viter des actions masques.
74
75
Execute; end; end; end; procedure TForm1.MaConnectionExecuteComplete(Connection: TADOConnection; RecordsAffected: Integer; const Error: Error; var EventStatus: TEventStatus; const Command: _Command; const Recordset: _Recordset); begin MessageDlg(IntToStr(RecordsAffected)+' modifications effectus',mtInformation,[mbOK],0); end; end. Le cheminement est simple, la cration de la feuille je dmarre une connexion asynchrone, lorsque celle-ci est complte elle lance s'il n'y a pas d'erreur la commande. Lorsque celle ci est termine, un message prcise le nombre de modifications effectues. Il y a trois avantages important avec ce style de programmation. Votre code peut excuter autre chose pendant que les oprations de longue dure ont lieu. Vous tes moins dpendant de la charge ventuelle du serveur. Les vnements contiennent tous les paramtres ncessaires la construction d'un code robuste.
76
Selon le mode choisi, je rcuprerai un signet sur le cinquantime enregistrement (eoAsyncFetchNonBlocking) ou sur le dernier (eoAsyncFetch). Aussi faut-il se mfier du type d'extraction que l'on choisit. J'ai mis en commentaire la ligne permettant la valeur du rapatriement initial car avec Delphi elle dclenche une erreur de violation d'accs.
Suivre l'extraction
Nous allons, dans cet exemple, utiliser une ProgressBar pour suivre l'extraction d'un recordset asynchrone. Pour cela nous allons utiliser l'vnement FetchProgress. Attention il faut travailler avec un curseur ct client. Le code est le suivant : procedure TForm1.Button1Click(Sender: TObject); begin with ADOConnection1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;'; CursorLocation:=clUseClient; ADOConnection1.ConnectOptions:=coAsyncConnect; Connected:=True; end; with ADODataset1 do begin Connection:=ADOConnection1; CursorLocation:=clUseClient; CursorType:=ctStatic; CommandText:='all Titles'; CommandType:= cmdTable; ExecuteOptions:=[eoAsyncFetch]; //Recordset.Properties['Background Fetch Size'].Value:=1000; Active:=True; end; end; procedure TForm1.ADODataSet1FetchProgress(DataSet: TCustomADODataSet; Progress, MaxProgress: Integer; var EventStatus: TEventStatus); begin Progressbar1.Position:=Trunc(Progress /MaxProgress *100); Application.ProcessMessages; end; procedure TForm1.ADODataSet1FetchComplete(DataSet: TCustomADODataSet; const Error: Error; var EventStatus: TEventStatus); begin datasource1.DataSet:=ADODataset1; end; Rien de bien compliqu avec ce type de codage.
77
begin Case Reason of erAddNew:; erClose:; erDelete:; erFirstChange:; erMove:; erRequery:; erResynch:; erUndoAddNew :; erUndoDelete:; erUndoUpdate:; erUpdate:; end; end; Le problme de se style de programmation est la difficult bien identifier la cause de la modification. Ainsi, si vous changez deux champs d'un mme enregistrement par l'intermdiaire d'une grille, l'vnement se produira deux fois, dans le premier cas avec l'argument adRsnFirstChange, ensuite avec adRsnUpdate. C'est pour cela que dans certains cas on prfre travailler sur l'vnement WillMove en testant la valeur EditMode du Recordset. Dans cet exemple, nous allons afficher un message de confirmation de validation chaque mouvement dans le jeu d'enregistrement. procedure TForm1.ADODataSet1WillMove(DataSet: TCustomADODataSet; const Reason: TEventReason; var EventStatus: TEventStatus); begin if (Dataset.State =dsEdit) or (Dataset.State =dsInsert) then if MessageDlg('Voulez vous enregistrer les modifications?', mtConfirmation,[mbYes,mbNo],0)=mrYes then Dataset.UpdateBatch(arCurrent) else Dataset.CancelBatch(arCurrent); end; end. Comme nous le voyons, ce sont des codes assez simples qui grent en gnral la programmation vnementielle. Je ne vais pas vous donner plus d'exemple de programmation vnementielle car elle doit tre adapte votre programme.
Recordset persistant
Nous allons ici nous reposer un peu en parlant des recordset persistants. Ceux-ci sont trs simples utiliser. Un recordset persistant est en fait un fichier de type "datagram" (format propritaire) ou XML que l'on peut crer partir d'un Dataset ADO ou ouvrir comme un Dataset. Ceci peut tre trs intressant dans les cas suivants : Comme scurit (comme nous allons le voir plus loin) Pour allger la connexion (puisqu'on peut le remettre jour par re synchronisation) Comme base de travail, si la mise jour n'est pas primordiale Le principe de fonctionnement est le suivant : procedure TForm1.FormCreate(Sender: TObject); begin with ADODataset1 do begin ConnectionString:='Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\biblio.mdb;';
78
procedure TForm1.btnSauveClick(Sender: TObject); var NomFichier:string; begin NomFichier:= 'd:\monJeu.dtg'; if FileExists(NomFichier) then DeleteFile(NomFichier); ADODataset1.Filter:='Author Like '+QuotedStr('s*'); ADODataset1.Filtered:=True; ADODataset1.SaveToFile(NomFichier,pfADTG); ADODataset1.Close; Datasource1.DataSet:=nil; end; procedure TForm1.btnChargeClick(Sender: TObject); var MonDataset:TADODataset; begin MonDataset:=TADODataset.Create(Owner); MonDataset.LoadFromFile('d:\monJeu.dtg'); Datasource1.DataSet:=MonDataset; end; Comme vous le voyez, il faut toujours s'assurer qu'il n'existe pas de fichier du mme nom sur le disque. Notez aussi qu'un recordset persistant garde l'ensemble des donnes et des mta-donnes, on peut ainsi rendre persistant un recordset ayant des modifications en attente, puis le rouvrir pour appliquer ces modifications sur la base. Dans mon exemple, j'utilise un Dataset pour ouvrir mon Fichier. Vous voyez que je ne lui passe aucun paramtre de connexion. Il est donc parfaitement dconnect. Il est possible de le connecter la source en lui passant une chane de connexion. Quelques prcisions sont encore ncessaires : Si le recordset une proprit Filter dfinie sans que celle-ci soit une constante prdfinies, seules les lignes accessibles avec ce filtre sont sauvegardes Pour sauvegarder plusieurs fois votre recordset, prcisez la destination seulement la premire fois. Si la deuxime fois, vous redonnez la mme destination vous obtiendrez une erreur, si vous en donnez une autre vous obtiendrez deux fichiers et le premier restera ouvert. La fermeture du fichier n'a lieu que lors de la fermeture du recordset. En programmation asynchrone, Save est toujours une mthode bloquante. Un recordset persistant garde ses possibilits de modifications sur la base. Toutefois, elles ne peuvent concerner qu'une table si le curseur est ct serveur. De plus, ce type de recordset ne pourra pas tre synchronis. Si votre recordset contient des champs de type "Variant" le recordset sauvegard ne sera pas exploitable.
Synchronisation
Lorsqu'on utilise un jeu d'enregistrements sans mise jour, de type statique ou en avant seulement, il est possible de ractualiser les donnes de celui-ci. On appelle cette technique la
79
synchronisation. Celle-ci n'est pas possible sur les Dataset ct client en lecture seule. Cette mthode est souvent mal utilise car son comportement peut sembler trange, aussi beaucoup de dveloppeurs prfre utiliser, tort, la mthode Requery. Un des problmes de cette mthode est qu'en fait, elle contient deux mthodes fondamentalement diffrentes selon les paramtres qu'on lui passe.
Synchronisation induite
Il y a deux autres cas de synchronisation qui ne sont pas explicitement demands par le dveloppeur. La mise jour des curseurs dynamique : Elle est dclenche par la valeur du Page TimeOut. Seule les valeurs sous jacentes sont rafrachies. Cette opration ne repositionne pas le jeu d'enregistrements. Les enregistrements dans le cache ne sont pas synchroniss. La mise jour aprs modification. Nous avons vu comment la dfinir l'aide de la proprit dynamique 'Update Resync'.
80
Gnration de commandes
Dans cet exemple nous allons crer une fonction utilisant l'objet TADOCommand afin de remplacer l'objet Dataset dans ces actions sur la source de donnes. Comme je l'ai dit auparavant, il est souvent prfrable d'agir par le biais de commande SQL plutt que par le moteur de curseur. Cependant, tel que nous allons pratiquer dans cet exemple, cela ne changera globalement rien car nous allons baser nos fonctions sur l'objet Dataset. Ceci aura pour intrt de voir comment fonctionne le moteur de curseur. Par la suite, nous verrons une approche pas les commandes paramtres beaucoup plus proches des techniques utilises par ADO.NET.
81
strNomChamp, strValChamp: TStrings; compteur, NbModif: Integer; MaCommande: TADOCommand; ValTemp:WideString; begin ptrMonJeu.Filtered:=False; ptrMonJeu.FilterGroup:=fgPendingRecords; ptrMonJeu.Filtered:=True; if ptrMonJeu.RecordCount=0 then exit; ptrMonJeu.First; MaCommande:=TADOCommand.Create(nil); strTable:=ptrMonJeu.recordset.Fields[0].Properties['BASETABLENAME']. Value; while not ptrMonJeu.Eof do begin ptrMonJeu.Recordset.Resync(1,1); if (rsNew in ptrMonJeu.RecordStatus) then begin strNomChamp:=TStringList.Create; strValChamp:=TStringList.Create; try for compteur := 0 to ptrMonJeu.FieldCount-1 do begin if ptrMonJeu.Fields[compteur].DataType<> ftAutoInc then begin strNomChamp.Add('[' + ptrMonJeu.Recordset.Fields[compteur].Name + ']'); if (ptrMonJeu.Fields[compteur].DataType= ftString) or (ptrMonJeu.Fields[compteur].DataType= ftWideString) then strValChamp.Add(QuotedStr(vartostr(ptrMonJeu.Recordset.Fields[compte ur].UnderlyingValue))) else if ptrMonJeu.Fields[compteur].DataType= ftDate then strValChamp.Add('#' + vartostr(ptrMonJeu.Recordset.Fields[compteur].UnderlyingValue)+ '#') else strValChamp.Add(vartostr(ptrMonJeu.Recordset.Fields[compteur].Underl yingValue)); end; end; strCommand:=StringReplace('INSERT INTO [' + strTable + '] (' + strNomChamp.CommaText +') VALUES(' + strValChamp.DelimitedText +')','"','',[rfReplaceAll]); finally strNomChamp.Free; strValChamp.Free; end; end else if (rsDeleted in ptrMonJeu.RecordStatus) then begin strNomChamp:=TStringList.Create; strValChamp:=TStringList.Create; try for compteur := 0 to ptrMonJeu.FieldCount-1 do begin
82
if strtobool(vartostr(ptrMonJeu.recordset.Fields[compteur].Properties[' KEYCOLUMN'].Value)) then strNomChamp.Add('[' + ptrMonJeu.Recordset.Fields[compteur].Name + ']'); if (ptrMonJeu.Fields[compteur].DataType= ftString) or (ptrMonJeu.Fields[compteur].DataType= ftWideString) then strValChamp.Add(QuotedStr(vartostr(ptrMonJeu.Recordset.Fields[compte ur].UnderlyingValue))) else if ptrMonJeu.Fields[compteur].DataType= ftDate then strValChamp.Add('#' + vartostr(ptrMonJeu.Recordset.Fields[compteur].UnderlyingValue)+ '#') else strValChamp.Add(vartostr(ptrMonJeu.Recordset.Fields[compteur].Underl yingValue)); end; strCommand:='DELETE FROM ' + strTable + ' WHERE ' + strNomChamp[0] +'='+strValChamp[0] ; if strNomChamp.Count>1 then for compteur:=1 to strNomChamp.Count-1 do strCommand:=strCommand + ' AND ' + strNomChamp[0] +'='+strValChamp[0] ; finally strNomChamp.Free; strValChamp.Free; end; end else begin strNomChamp:=TStringList.Create; strValChamp:=TStringList.Create; try for compteur := 0 to ptrMonJeu.FieldCount-1 do begin Valtemp:='[' + ptrMonJeu.Recordset.Fields[compteur].Name + '] = '; if ptrMonJeu.Fields[compteur].CurValue<>ptrMonJeu.Fields[compteur].NewV alue then begin if (ptrMonJeu.Fields[compteur].DataType= ftString) or (ptrMonJeu.Fields[compteur].DataType= ftWideString) then strNomChamp.Add(Valtemp+QuotedStr(vartostr(ptrMonJeu.Recordset.Field s[compteur].Value))) else if ptrMonJeu.Fields[compteur].DataType= ftDate then strNomChamp.Add(Valtemp+'#' + vartostr(ptrMonJeu.Recordset.Fields[compteur].Value)+ '#') else strNomChamp.Add(Valtemp+vartostr(ptrMonJeu.Recordset.Fields[compteur ].Value)); end;
83
case Critere of 1: begin// juste les champs cls if strtobool(vartostr(ptrMonJeu.recordset.Fields[compteur].Properties[' KEYCOLUMN'].Value)) then begin if (ptrMonJeu.Fields[compteur].DataType= ftString) or (ptrMonJeu.Fields[compteur].DataType= ftWideString) then strValChamp.Add(Valtemp+QuotedStr(vartostr(ptrMonJeu.Recordset.Field s[compteur].UnderlyingValue))) else if ptrMonJeu.Fields[compteur].DataType= ftDate then strValChamp.Add(Valtemp+'#' + vartostr(ptrMonJeu.Recordset.Fields[compteur].UnderlyingValue)+ '#') else strValChamp.Add(Valtemp+vartostr(ptrMonJeu.Recordset.Fields[compteur ].UnderlyingValue)); end; end; 2: begin// juste les champs cls et les champs modifis if (strtobool(vartostr(ptrMonJeu.recordset.Fields[compteur].Properties[ 'KEYCOLUMN'].Value))) or (ptrMonJeu.Fields[compteur].CurValue<>ptrMonJeu.Fields[compteur].New Value) then if (ptrMonJeu.Fields[compteur].DataType= ftString) or (ptrMonJeu.Fields[compteur].DataType= ftWideString) then strValChamp.Add(Valtemp+QuotedStr(vartostr(ptrMonJeu.Recordset.Field s[compteur].UnderlyingValue))) else if ptrMonJeu.Fields[compteur].DataType= ftDate then strValChamp.Add(Valtemp+'#' + vartostr(ptrMonJeu.Recordset.Fields[compteur].UnderlyingValue)+ '#') else strValChamp.Add(Valtemp+vartostr(ptrMonJeu.Recordset.Fields[compteur ].UnderlyingValue)); end; 3: begin// tous les champs if (ptrMonJeu.Fields[compteur].DataType= ftString) or (ptrMonJeu.Fields[compteur].DataType= ftWideString) then strValChamp.Add(Valtemp+QuotedStr(vartostr(ptrMonJeu.Recordset.Field s[compteur].UnderlyingValue))) else if ptrMonJeu.Fields[compteur].DataType= ftDate then strValChamp.Add(Valtemp+'#' + vartostr(ptrMonJeu.Recordset.Fields[compteur].UnderlyingValue)+ '#') else
84
strValChamp.Add(Valtemp+vartostr(ptrMonJeu.Recordset.Fields[compteur ].UnderlyingValue)); end; end; end; strCommand:='UPDATE ' + strTable + ' SET ' + strNomChamp[0]; if strNomChamp.Count>1 then begin for compteur:=1 to strNomChamp.Count-1 do strCommand:=strCommand + ' AND ' + strNomChamp[compteur]; end; strCommand:=strCommand + ' WHERE ' + strValChamp[0]; if strValChamp.Count>1 then begin for compteur:=1 to strValChamp.Count-1 do strCommand:=strCommand + ' AND ' + strValChamp[compteur]; end; finally strNomChamp.Free; strValChamp.Free; end; end; with MaCommande do begin Connection:=ptrMonJeu.Connection; CommandType:=cmdText; CommandText:=strCommand; Try Execute(NbModif,EmptyParam); Except ptrMonJeu.CancelBatch(arCurrent); end; end; ptrMonJeu.CancelBatch(arCurrent); ptrMonJeu.Next; end; MaCommande.Free; end; Comme vous le voyez, je ne passe aucune information ma fonction qui n'est pas contenu dans le DataSet. Le principe de fonctionnement est simple. Je filtre pour n'avoir que les enregistrements ayant une modification en cours. Attention, les informations de schma contenues dans le Dataset ne sont pertinentes que parce que j'utilise un curseur cot client avec une mise jour par lot. Je vais ensuite balayer mon Dataset est crire une commande refltant l'action demande. Le code peut sembler un peu abscons, mais il ne s'agit que de l'criture d'une requte action. Il s'agit l videmment d'un exemple assez simple. En ajoutant quelques informations de schma, je pourrais crire un gnrateur de requte plus efficace que celui-la qui permettrait une criture de requte multi-table.
Commande paramtre
Cette deuxime approche est beaucoup plus claire. Elle ressemble l'approche ADO.NET de programmation. Dans ADO.NET on utilise des commandes paramtres pour agir sur la source de donnes. Il existe de nombreuses mthodes pour aborder ce type de stratgies que j'aborderai dans un autre article sur ADO.NET mais nous allons tudier le principe gnral ainsi que les piges viter.
85
A la base, soit on travaille avec une commande " la vole" soit on passe par des fonctions. Les deux techniques prsentent des avantages, mais dans le cas qui m'intresse, je vais utiliser des fonctions. Dans ADO.NET on gre autant de commande que de requte action, il n'y a alors plus qu' passer les paramtres la commande pour grer celle-ci. Dans une approche plus volue, je peux gnrer une commande entirement modulable, donc similaire au moteur de curseur en utilisant un code de type ADO.NET. Prenons l'exemple suivant qui gre une requte 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 gre une commande par fonction. C'est un code trs sur mais pas trs souple puisque si je veux pouvoir modifier de nombreuses tables je vais me retrouver grer autant de fonctions que de tables pour un seul type d'action. Je peux donc concevoir
86
une fonction beaucoup plus puissante, rpondant aux modifications de plusieurs tables. Toujours dans le mme ordre d'ide, 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 paramtre : Un tableau de variant contenant le nom de la table puis les noms des champs modifis puis les noms des champs, puis la liste des valeurs de ces champs dans le mme ordre. Un entier reprsentant le nombre de champs modifis. Si cet entier vaut zro, 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 srieusement, cela est aussi trs efficace. Bon, o en taisje avant d'tre brutalement interrompu par votre remarque sur ma faon 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']]); RecValue:=CommUpdate(TabParam,1);
87
Voil qui devrait finir de dprimer 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 chvre. Prenons de l'avance sur mon exemple suivant et imaginons que nous souhaitions ajouter une valeur alatoire l'anne 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 modifis 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 compltement 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 mmes raisons que leurs homologues ADO.NET. En effet, que va-t-il se passer si une des valeurs est NULL. Ma fonction crit une chane de commande, dans mon exemple, qui est :
88
'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 gnrer 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 faon 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 srement un peu fastidieux, mais maintenant vous voyez exactement comment fonctionne le moteur de curseur et les commandes paramtres, nous allons pouvoir rentrer dans des sujets un peu plus complexes.
89
90
Active:=True; Filter:='[year born] = 1938'; Filtered:=True; if RecordCount>0 then begin First; while not Eof do begin Delete; Next; end; end; Filtered:=False; Filter:='[year born] <> 1938'; Filtered:=True; First; while not Eof do begin Edit; FieldByName('year born').Value:=adoRecordset.FieldByName('year born').Value+RandomRange(1,5); Post; Next; end; SaveToFile('d:\temp.adtg',pfADTG); Filtered:=False; TestErr:=False; try UpdateBatch(arAll); except adoErreurs:= adoConnection.Errors; adoCommand:=TADOCommand.Create(nil); For compteur:=0 to adoErreurs.Count - 1 do if adoErreurs[compteur].Number <>-2147467259 Then TestErr:=True; if Not TestErr then begin FilterGroup:=fgConflictingRecords; Filtered:=True; if RecordCount>0 then MessageDlg(IntToStr(adoRecordset.Recordset.RecordCount), mtInformation,[mbOK], 0); Filtered:=False; FilterGroup:=fgPendingRecords; Filtered:=True; if RecordCount>0 then begin First; while not Eof do begin if rsDeleted in adoRecordset.RecordStatus then adorecordset.CancelBatch(arCurrent); With adoCommand do begin Connection:=AdoConnection; CommandType:=cmdText; CommandText:='DELETE * FROM Titles WHERE ISBN IN (SELECT ISBN FROM [Title Author] WHERE Au_Id =' + adoRecordset.FieldByName('Au_Id').AsString + ')';
91
Execute; CommandText:='DELETE * FROM [Title Author] WHERE Au_Id= ' + adoRecordset.FieldByName('Au_Id').AsString; Execute; adorecordset.Delete; adorecordset.UpdateBatch(arCurrent); end; Next; end; end; end else begin Close; LoadFromFile('d:\temp.adtg'); Connection:=adoConnection; First; adoCommand.Connection:=AdoConnection; adoCommand.CommandType:=cmdText; adoCommand.CommandText := 'UPDATE Authors SET [year born] =:ValCible WHERE Au_Id = :ValCle AND [year born]= :ValSource'; while not EOF do begin if (rsModified in adoRecordset.RecordStatus) then begin Cancel; Recordset.Resync(1,1); if FieldByName('year born').OldValue<>FieldByName('year born').CurValue then adoCommand.Execute(VarArrayOf([FieldByName('year born').OldValue,FieldByName('Au_Id').CurValue,FieldByName('year born').CurValue])); end; next; end; end; end; end; end; Rien que du bonheur. Rentrons dans l'tude dtaille. Jusqu' l'appel de la mthode UpdateBatch rien de nouveau. Tout le code de l'exception vient de la volont d'avoir une opration globalement accepte ou globalement rejete. Je commence toujours par mettre les erreurs ado dans une collection indpendante car Une nouvelle erreur effacerait l'ancienne collection Le parcours de la collection sur la connexion tend planter l'excution. Je parcours ensuite ma collection avec le code : if adoErreurs[compteur].Number <>-2147467259 Then TestErr:=True; En cherchant une erreur de numro 2147467259, je recherche une suppression illgale de type tentative de suppression en cascade. Attention, ce numro d'erreur pourrait aussi tre obtenu avec une erreur de dfinition de fournisseur, mais une telle erreur se produirai avant le Try. Si seul ce type d'erreur est prsent sur la connexion, je peux poursuivre l'opration sinon je vais devoir restaurer. Il est important de noter qu' ce point, les oprations valides ont dj t effectues. Le premier filtrage, par les enregistrements en erreur, qui est la faon standard de procder est mise ici titre d'exemple car elle ne fonctionnera pas dans ce cas. En effet, ce type d'erreur n'est
92
jamais attribu un enregistrement spcifique, ce qui est d'ailleurs une incohrence ADO. Regardons quand mme ici le cas standard de gestion des erreurs
93
First; while not EOF do begin Statut:= trunc(power(2,trunc(log2(Recordset.Status)))); while Statut>8 do begin Case Statut of 16: showmessage('invalide');//RsInvalid 64: showmessage('invalide'); // RsMultipleChanges 128: showmessage('invalide');// RsPendingChanges 256: showmessage('invalide'); // RsCanceled 1024: showmessage('invalide'); // RsCantRelease 2048: showmessage('invalide'); // RsConcurrencyViolation 4096: showmessage('invalide'); // RsIntegrityViolation 8192: showmessage('invalide'); // RsMaxChangesExceeded 16384: showmessage('invalide'); // RsObjectOpen 32768: showmessage('invalide'); // RsOutOfMemory 65536: showmessage('invalide'); // RsPermissionDenied 131072: showmessage('invalide'); // RsSchemaViolation 262144: showmessage('invalide'); // RsDBDeleted end; Statut:=Recordset.Status-Statut; end; Next; end; end; end; end; Nanmoins je ne suis pas oblig d'utiliser une structure Case. Le mme code pourrait travailler directement sur la proprit RecordStatus avec une structure if. while not EOF do begin if (RsInvalid in Recordstatus) then showmessage('invalide');//RsInvalid if (RsMultipleChanges in Recordstatus) then showmessage('invalide');//RsMultipleChanges if (RsPendingChanges in Recordstatus) then showmessage('invalide');//RsPendingChanges if (RsCanceled in Recordstatus) then showmessage('invalide');//RsCanceled if (RsCantRelease in Recordstatus) then showmessage('invalide');//RsCantRelease if (RsConcurrencyViolation in Recordstatus) then showmessage('invalide');//RsConcurrencyViolation if (RsIntegrityViolation in Recordstatus) then showmessage('invalide');//RsIntegrityViolation if (RsMaxChangesExceeded in Recordstatus) then showmessage('invalide');//RsMaxChangesExceeded if (RsObjectOpen in Recordstatus) then showmessage('invalide');//RsObjectOpen if (RsOutOfMemory in Recordstatus) then showmessage('invalide');//RsOutOfMemory if (RsPermissionDenied in Recordstatus) then showmessage('invalide');//RsPermissionDenied if (RsSchemaViolation in Recordstatus) then
94
Actions correctives
Ds lors que je dois grer des actions correctives selon mes erreurs, il y a globalement trois dcisions possibles.
Pas de gestion
Je peux ignorer l'erreur, considrant que celle-ci ne porte pas consquence. C'est un choix qu'il convient de bien peser. Ce genre de pratique est viter mes yeux, mais dans certains cas c'est le seul choix judicieux surtout lorsqu'il y a possibilit de concurrence. Par exemple, si un enregistrement a t supprim par un autre utilisateur, il y aurait incohrence recrer celui-ci par une correction.
95
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 WHERE [year born] IS NOT NULL'; Active:=True; Filter:='[year born] = 1938'; Filtered:=True; if RecordCount>0 then begin First; while not Eof do begin Delete; Next; end; end; Filtered:=False; Filter:='[year born] <> 1938'; Filtered:=True; First; while not Eof do begin Edit; FieldByName('year born').Value:=adoRecordset.FieldByName('year born').Value+RandomRange(1,5); Post; Next; end; Filtered:=False; Connection.BeginTrans; try UpdateBatch(arAll); Connection.CommitTrans; except Connection.RollbackTrans; end; end; end; Comme nous le voyons, c'est en effet nettement plus simple. Il serait d'ailleurs possible d'imbriquer des transactions pour pouvoir valider des modifications partielles.
Accs concurrentiel
C'est l un immense sujet, bien trop vaste pour tre trait en dtail dans cet article. On peut concevoir l'aphorisme suivant de Frdric Brouard comme une bonne base de dpart.
96
Le verrouillage
Nous avons dj vu ce qu'il en tait de la thorie du verrouillage, au moins superficiellement car le verrouillage est une technique particulirement pigeuse, tel que je vous l'ai dj montr dans quelques exemples. Rappelons-nous tout d'abord que les verrous optimistes ne sont pas stricto sensu des verrous. D'autant plus qu'ADO dans ces curseurs avec rafrachissement va se faire un malin plaisir de vous les faire sauter.
97
N.B : Certaines versions d'Access utilisent le verrouillage par page. C'est la cerise sur le gteau. Cela revient dire que lors du verrouillage d'un enregistrement, d'autres peuvent l'tre aussi sans aucune raison.
Transactions et exclusivit
Il s'agit l des vrais techniques de gestion scurise de la concurrence. Je ne vais pas redire ici ce que j'ai dj dit plus haut sur les transactions et sur l'isolation de celles-ci. Sachez toutefois que vous devez travailler avec des transactions lorsqu'il y a risque de concurrence. Cette rgle ne doit pas tre contourne. L'exclusivit consiste restreindre les droits des nouveaux entrants, au niveau de leurs connexions ou verrouiller une ou plusieurs tables le temps de faire ses modifications. L'une comme l'autre de ces techniques est dconseiller car au lieu de rgler le problme de la concurrence elles crent implicitement des accs privilgis, ce qui ne doit tre fait qu'en toute connaissance de cause
98
99
Modle objet
Le modle objet que nous allons regarder est le suivant :
Comme vous le voyez, il n'y pas de similitude entre les deux modles objet, si ce n'est de nouveau la prsence des collections "Properties" de proprits dynamiques. Cependant, si vous avez bien assimilez ce que nous avons vu jusque l, vous n'allez rencontrer aucune difficults pour utiliser le Modle ADOX.
Rappels Access
Scurit
Il y a deux types de scurit Access : la scurit au niveau partage, c'est dire l'utilisation d'un mot de passe pour ouvrir la base de donnes et la scurit au niveau utilisateur semblable aux autres SGBD. Celle-ci repose sur l'identification de l'utilisateur, celui-ci ayant un certain nombre de droits dfinis. La diffrence d'Access vient de l'utilisation d'un fichier externe pour grer cette scurit. Les deux niveaux de scurit sont cumulables.
Paramtrage JET
Par dfaut, le paramtrage du moteur Jet se trouve dans la base de registre la cl : HKLM\Software\Microsoft\Jet\4.0\Engines\Jet 4.0. Ce paramtrage pouvant tre diffrent d'un poste l'autre, il convient d'utiliser le code pour toujours mettre ce paramtrage aux valeurs dsires. Pour cela deux approches sont possibles : la
100
modification des valeurs du registre ou le paramtrage de la connexion (mthode que nous verrons plus loin).
Notions Fondamentales
ADOX & Access
Dans ce modle, il y a une certaine redondance. Dans le cas d'Access, une requte non paramtre renvoyant des enregistrements est considre comme une table de type "VIEW" (vues). Une telle requte apparatra donc comme membre de la collection "Table" (un peu diffrente des autres tables) et comme membres de la collection "Views" mais pas comme membre de la collection procdure. Attention, dans Access il faut considrer une procdure comme une requte paramtre ou une requte ne renvoyant pas d'enregistrement (Requte action par exemple) ; n'attendez pas de rcuprer des procdures VBA dans cette collection.
Propritaire
Cette notion de proprit est trs importante. En effet seul le propritaire d'un objet peut faire certaines modifications sur celui-ci et attribuer les droits sur ces objets. Par dfaut, le propritaire d'un objet est son crateur. Cela implique deux choses : Ne pas laisser aux utilisateurs le droit de crer des objets dans vos bases sans un contrle car vous n'auriez pas de droit de modifications sur ces objets sans en reprendre la proprit. Grer strictement les droits sur la base. Nous y reviendrons plus en dtail lorsque nous aborderons la scurit.
ParentCatalog
Lorsque l'on cre des objets, ils ne sont relis aucun catalogue. Ds lors, comme ils ne connaissent pas leurs fournisseurs, il n'est pas possible de valoriser leurs proprits. On dfinit donc la proprit ParentCatalog d'un objet ds que l'on souhaite valoriser ses proprits. Ceci pourtant dissimule un problme d'un fort beau gabarit ma foi. Outre le fournisseur, le catalogue transmet aussi ses droits de propritaire. Si on n'y prend pas garde, il se peut que l'objet se retrouve ayant des droits non souhaits.
L'objet Catalog
C'est l'objet racine du modle. Attention, nous n'allons utiliser que de la programmation intrinsque.
ActiveConnection
C'est la seule proprit de l'objet Catalog, qui lui permet de dfinir le fournisseur de donnes, la base de donnes et ventuellement les paramtres de scurit. Cette connexion est stricto sensu une connexion ADO, on peut donc parfaitement utiliser une connexion existante. Attention toutefois, ce sont les paramtres de la connexion qui dfinissent en partie les possibilits du fournisseur, il est donc vivement recommand de crer sa connexion pour la gestion de la scurit. Le code suivant permet de crer un catalogue sur une base existante :
procedure TForm1.Button1Click(Sender: TObject); var MonCat: _catalog; begin MonCat:=coCatalog.Create; moncat.Set_ActiveConnection('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\tutoriel\biblio.mdb;'); end; L'exemple d'aprs cre une base de donne Access
101
procedure TForm1.Button1Click(Sender: TObject); var MonCat: _catalog; begin if FileExists('C:\tutoriel\nouv.mdb') then DeleteFile('C:\tutoriel\nouv.mdb'); MonCat:=coCatalog.Create; moncat.create('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\tutoriel\nouv.mdb;'); end; Create (Mth.) Permet la cration d'une nouvelle base de donnes selon les paramtres de la chane de connexion passe en paramtre. Attention, le nom de la base de donnes ne doit pas tre une base existante. Le paramtre passer la mthode doit tre une chane de connexion valide. GetObjectOwner & SetObjectOwner (Mth.) De la forme Catalog.SetObjectOwner ObjectName, ObjectType , OwnerName [, ObjectTypeId] Owner = Catalog.GetObjectOwner(ObjectName, ObjectType [, ObjectTypeId]) Permet de renvoyer ou de dfinir le propritaire d'un objet de la base, celui-ci pouvant tre la base de donnes, une table, un champ.. Ne fonctionne qu'avec un fichier de scurit dfini. Les collections L'objet Catalog renvoie aussi cinq collections qui reprsentent la structure de la base. Nous allons maintenant tudier ces objets
Append
Ajoute un objet la collection. La mthode Append est propre chaque collection, aussi allons nous la voir en dtail plus loin. Notons toutefois qu'un objet cr est librement manipulable, tant qu'il n'a pas t ajout la collection correspondante. Aprs l'ajout, un certain nombre de proprits passe en lecture seule.
Item
Renvoie un lment d'une collection par son nom ou son index.
Delete
Retire un lment d'une collection. Il faut faire trs attention la suppression de certains lments qui peut mettre en danger l'intgrit des donnes voire endommager la base
Refresh
Met jour la collection.
Collection tables
Reprsente l'ensemble des tables, au sens large du terme, qui compose la base de donnes. Ces tables peuvent tre ranges dans les familles suivantes : Table systme Table Temporaire Table de donnes
102
Vues En gnrale on ne travaille que sur les tables de donnes l'aide de l'objet Table.
Append
De la forme Cat.Tables.Append NomTable. Lors de la cration tous les objets appartenant la table doivent tre crs avant l'ajout de celle-ci la collection
Collection Procedures
Reprsente l'ensemble des requtes dfinies dans la base de donnes l'exclusion des requtes non paramtres renvoyant des enregistrements.
Append
De la forme Cat.Procedures.Append NomRequete, Command. Command reprsente un objet command qui reprsente la requte.
Collection Views
Reprsente l'ensemble des requtes non paramtres renvoyant des enregistrements. On les retrouve d'une certaine faon dans la collection Tables avec Access.
Append
De la forme Cat.Views.Append NomRequete, Command. Command reprsente un objet command qui reprsente la requte.
Collection Groups
Reprsente l'ensemble des groupes dfinis dans la base. A ne pas confondre avec la collection Groups de l'objet User qui est le groupe auquel appartient un utilisateur.
Append
De la forme Cat.Groups.Append NomGroupe. Ceci reprsente l'ajout d'un groupe la base de donnes. Les permissions doivent tre dfinies lors de l'ajout du groupe la collection. Le groupe doit tre ajout la collection avant la cration d'un utilisateur du groupe.
Collection Users
Reprsente l'ensemble des utilisateurs dfinis dans la base. A ne pas confondre avec la collection Users de l'objet Group qui est l'ensemble des utilisateurs dfinis dans un groupe.
Append
De la forme Cat.Users.Append NomUtilisateur [, MotDePasse]. Ceci reprsente l'ajout d'un utilisateur la base de donnes.
L'objet Table
Cet objet reprsente une table de la base de donnes. Les tables de type "VIEW" seront vues dans le chapitre du mme nom. Nous allons donc regarder les tables Standard. Une table contient des champs (colonnes), des index, des cls et des proprits propres au fournisseur. Seules deux proprits intressantes sont statiques : Name qui reprsente le nom de la table et est obligatoire, et type qui porte bien son nom mais qui est en lecture seule. Ceci fait qu'il n'y a pas de questions existentielles se poser, lorsqu'on cre une table avec ADOX, elle est forcment standard.
103
La table n'existe rellement dans la base de donnes que lorsque Son catalogue est dfini Elle est ajoute la collection Tables. Il convient donc de crer entirement la table avant de l'ajouter la collection.
Collection Properties
Cette collection n'est utilise que dans le cas des tables lies. N'essayez pas de crer des tables lies en partant du catalogue de votre base, bien que techniquement possible cela n'est pas conseill. Le code suivant cre une table lie procedure TForm1.Button1Click(Sender: TObject); var MonCat: _catalog; MaTable: _table; begin MonCat:=coCatalog.Create; moncat.Set_ActiveConnection('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\tutoriel\tuto.mdb;Jet OLEDB:System database=c:\tutoriel\system.mdw;User Id=Admin; Password='); MaTable:=coTable.Create; With MaTable do begin Name:='Auteurs'; ParentCatalog:=MonCat; Properties['Jet OLEDB:Create Link'].Value:=True; Properties['Jet OLEDB:Link Datasource'].Value:= 'C:\tutoriel\biblio.mdb'; Properties['Jet OLEDB:Link Provider String'].Value:=';Pwd=password'; Properties['Jet OLEDB:Remote Table Name'].Value:='Authors'; end; moncat.Tables.Append(matable); end; Lorsque j'cris que c'est dconseill, ce n'est pas d la complexit du code mais pour les deux raisons suivantes : Il faut s'assurer que le fournisseur utilis pour faire la liaison sera prsent sur le poste qui excute. Il faut bien savoir si les paramtres de scurit de liaison seront ou ne seront pas stocks de faon permanente dans la base. Regardons ce code. Aprs avoir dfini un catalogue nous crons un objet Table. Dans cet exemple, le nom est diffrent du vrai nom de la table dans l'autre base, mais cela n'a aucune importance. Ensuite je lui attribue un catalogue afin de pouvoir valoriser ses proprits dynamiques. Notez bien que j'utilise l'objet Catalog de ma base car je cre une table lie dans ma base. Je valorise ensuite mes proprits dynamiques, notons que je ne mets pas de fournisseur dans "Jet OLEDB:Link Provider String" car je continue d'utiliser Jet mais je pourrais tout fait changer le fournisseur. Enfin j'ajoute ma table la collection. A partir de cet ajout, la table lie est cre mais un certain nombre de ses proprits sont passes en lecture seule.
Collection Columns
Il existe des collections/objets Column pour l'objet Table, Index et Key. Attention de ne pas les confondre. La collection Columns regroupe les champs de la table. Elle possde les proprits/mthodes d'une collection normale. Append (mth.) : De la forme Columns.Append Column [, Type] [, DefinedSize] O Column est l'objet colonne ajouter.
104
Objet Column
Reprsente dans ce cas un champ de la table. A ne pas confondre avec l'objet Column de l'index. Cet objet n'a pas de mthode.
Proprits Attributes
Dfini si la colonne est de longueur fixe et si elle accepte les valeurs NULL. En gnral avec Delphi il est plutt conseill d'utiliser les proprits dynamiques Nullable et Jet OLEDB:Allow Zero Length.
DefinedSize
Dfini la taille maximum du champ pour une chane de caractres.
Name
Nom de la table. Obligatoire. L'unicit d'un nom dans une collection n'est pas obligatoire, mais dans une mme base, il peut pas y avoir deux tables portant le mme nom.
NumericScale
A ne pas confondre avec la proprit Prcision. Donne l'chelle (nombre de chiffre aprs la virgule)d'un champ dont le type est adNumeric ou adDecimal.
ParentCatalog
Qu'on ne prsente plus!
Prcision
Dfini la prcision (nombre maximal de chiffre pour reprsenter la valeur)d'un champ numrique.
105
Type
Dfini le type du champ. Il existe beaucoup de types dfinis mais le tableau suivant ne vous donnera que les principaux types. Type Valeur Commentaire adSmallInt 2 Type Entier court adInteger 3 Type Entier long (Access) adSingle 4 Valeur dcimale simple prcision adDouble 5 Valeur dcimale double prcision adCurrency 6 Type montaire. Utilise normalement 4 chiffres aprs la virgule adDate 7 Stock comme un double. La partie entire tant le nombre de jours depuis le 30/12/1899 adBSTR 8 Pointeur de BSTR. Pour plus de prcisions sur ce type, consulter la Rfrence du Programmeur OLE DB. adError 10 Code d'erreur 32-bit (DBTYPE_ERROR). adBoolean 11 Type Variant Boolean. 0 est faux et ~0 est vrai. adDecimal 14 Type variant dcimal. Pour plus de prcisions sur ce type, consulter la Rfrence du Programmeur OLE DB (DBTYPE_DECIMAL). adTinyInt 16 Valeur numrique exacte, de prcision 3 et d'chelle 0 (DBTYPE_I1). adUnsignedTinyInt 17 Version non signe de adTinyInt (DBTYPE_UI1). adUnsignedSmallInt 18 Version non signe de adSmallInt (DBTYPE_UI2). adUnsignedInt 19 Version non signe de adInteger (DBTYPE_UI4). adBigInt 20 Valeur numrique exacte, de prcision 19 et d'chelle 0 (DBTYPE_I8). adUnsignedBigInt 21 Version non signe de adBigInt (DBTYPE_UI8). adFileTime 64 Valeur 64-bit value reprsentant le nombres d'intervalle de 100nanosecond depuis le 1 Janvier 1601 (DBTYPE_FILETIME). adGUID 72 Identificateur Globalement Unique (GUID = Globally Unique Identifier) (DBTYPE_GUID). adBinary 128 Donnes binaires de longueur fixe (DBTYPE_BYTES). adChar 129 Chane de caractres de longueur fixe (DBTYPE_STR). adWChar 130 Chane de caractres unicode de longueur fixe (DBTYPE_WSTR). adNumeric 131 Type numrique. Pour plus de prcisions sur ce type, consulter la Rfrence du Programmeur OLE DB (DBTYPE_NUMERIC). adUserDefined 132 Type de donne de longueur variable dfini par l'utilisateur (DBTYPE_UDT). adDBDate 133 Structure de date pour base de donnes (yyyymmdd) (DBTYPE_DBDATE). adDBTime 134 Structure de temps pour base de donnes (hhmmss) (DBTYPE_DBTIME). adDBTimeStamp 135 Structure de marquage de temps pour base de donnes (yyyymmddhhmmss plus une fraction) (DBTYPE_DBTIMESTAMP). adChapter 136 Indique une valeur 4-bit de chapitre qui identifie une ligne d'un jeu d'enregistrement enfant (DBTYPE_HCHAPTER). adVarWChar 202 Chane de caractres (type Text Access) adLongVarWChar 203 Champs mmo (Access) et lien hypertexte adLongVarBinary 205 Type OLE Object Access
106
Collection Properties
Les proprits dynamiques de l'objet Column intressantes sont : Autoincrement : Permet de crer les colonnes numrotation automatique. Default : Dfinit la valeur par dfaut d'un champ. Description : Donne accs la description de la colonne, si le SGBD les grent. Nullable : Dfinit si la colonne accepte des valeurs NULL. Fixed Length : Dfinit si la colonne est de longueur fixe. Seed : Dtermine la prochaine valeur qui sera fourni par un champ de type numro Automatique. Increment : Dfinit le pas d'incrment d'un champ NumroAuto. Jet OLEDB:Column Validation Text : Dfinit le message d'alerte si une rgle de validation n'est pas respecte. Jet OLEDB:Column Validation Rule : Dfinit une rgle de validation pour un champ. Jet OLEDB:Allow Zero Length : Dfinit si les chanes vides sont autorises.
Exemple
Dans l'exemple suivant, nous allons crer une table contenant trois champs, 'Id' de type NumroAuto, 'Nom' de type Texte et obligatoire, 'Num Tel' de type texte et facultatif. var MonCat: _catalog; MaTable: _table; ColId, ColNom, ColTel: _column; begin MonCat:=coCatalog.Create; moncat.Set_ActiveConnection('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\tutoriel\tuto.mdb;Jet OLEDB:System database=c:\tutoriel\system.mdw;User Id=Admin; Password='); MaTable:=coTable.Create; MaTable.Name:='TestColonne'; ColId:=coColumn.Create; ColId.ParentCatalog:=MonCat; With ColId do begin Name:='Id'; Type_:=adInteger; Properties['Nullable'].Value:=False; Properties['Autoincrement'].Value:=True; Properties['Seed'].Value:=1; Properties['Increment'].Value:=1; end; ColNom:=coColumn.Create; ColNom.ParentCatalog:=MonCat; With ColNom do begin Name:='Nom'; Type_:=adVarWChar; DefinedSize:=50; Properties['Nullable'].Value:=False; Properties['Jet OLEDB:Allow Zero Length'].Value:=False; end; ColTel:=coColumn.Create; ColTel.ParentCatalog:=MonCat; With ColTel do begin Name:='Num Tel'; Type_:=adVarWChar;
107
DefinedSize:=14; Properties['Nullable'].Value:=True; Properties['Jet OLEDB:Allow Zero Length'].Value:=True; end; MaTable.Columns.Append(ColId,Unassigned,Unassigned); MaTable.Columns.Append(ColNom,Unassigned,Unassigned); MaTable.Columns.Append(ColTel,Unassigned,Unassigned); moncat.Tables.Append(matable); end; Je commence donc par crer le catalogue de la base de donnes. Pour cela j'utilise une chane de connexion qui est obligatoire pour pouvoir utiliser la mthode Create de catalogue. Ensuite je cre mon objet Table. Comme je n'utilise pas sa collection Properties, je ne valorise pas sa proprit ParentCatalog. Pour chaque colonne je dfinis une taille, un nom, un type et ventuellement ses attributs. Notons que comme la colonne n'est pas encore ajoute la collection Columns, elle n'existe pas encore. Ds lors, l'ordre de valorisation des proprits n'a aucune importance. Dans le cas de la colonne "Id" je vais crer un champ NumroAuto. Pour cela, je dois valoriser des proprits dynamiques, donc je dois dfinir la proprit ParentCatalog. Enfin j'ajoute la colonne la collection Columns puis la table la collection Tables. Notez aussi que tous ce code revient au mme, l'exception de la cration de la base que la commande DDL pour peu que le DDL normalis soit support par le SGBD.
"CREATE TABLE PremTable(Id COUNTER, Nom TEXT 50 NOT NULL, Tel TEXT 14)"
Collection Indexes
Reprsente les index de la table. Faites bien attention ne pas confondre index et cl. Un index sert augmenter l'efficacit du SGBDR lors des recherches, de la mise en relation etc. Normalement devrait toujours tre indexs v Les colonnes composantes d'une cl v Les colonnes ayant des contraintes de validit ou d'unicit v Les colonnes tant la cible de nombreuses recherches. Les proprits/mthodes de cette collection sont celles des collections standard si ce n'est :
Append
De la forme : Indexes.Append Index [, Columns] O Columns renvoie le ou les noms des colonnes devant tre indexes. En gnral, on dfinit l'objet colonnes avant son ajout la collection
Objet Index
Cet objet n'a pas de mthodes. Attention, on ne peut utiliser un objet Column avec cet objet que si la colonne appartient dj la collection Columns de l'objet Table.
Clustered
Indique si l'index est regroup. Un index regroup signifie qu'il existe un ordre physique pour les donnes. C'est en gnral l'index de la cl primaire.
IndexNulls
Dfinit le comportement de l'index vis vis des colonnes ayant une valeur NULL.
PrimaryKey
Prcise si l'index reprsente la cl primaire de la table. NB : C'est l que je vous disais de faire attention. Une cl primaire (ou non d'ailleurs) est dfinie par la collection Keys. Nanmoins on dfinit en gnral aussi un index sur la colonne de cl 108
primaire. En mettant vrai cette proprit, la cration de l'index implique la cration de la cl. Donc si vous crez une cl primaire de faon implicite n'essayez pas de la recrer aprs dans la collection Keys et vice versa.
Unique
Dfinit la contrainte d'unicit.
Collection Properties
Comme toujours, la proprit ParentCatalog de l'index doit tre valoris avant l'appel d'un membre de cette collection. La plupart de ces proprits tant en lecture seule ou redondante avec les proprits statiques de l'objet, nous n'irons pas plus avant dans notre exploration.
SortOrder
Dfinit l'ordre de tri d'une colonne d'index.
Exemple
Dans mon exemple prcdent je vais rajouter un index sur la colonne Nom, avec un tri croissant. MonIndex:=coIndex.Create; with MonIndex do begin IndexNulls:=adIndexNullsDisallow; Name:='ind1'; Columns.Append(ColNom.Name, ColNom.type_, ColNom.DefinedSize); Columns['Nom'].SortOrder:= adSortAscending; end; MaTable.Indexes.Append(MonIndex,EmptyParam); Ce code se situe aprs l'ajout des colonnes l'objet Tables mais avant l'ajout de l'objet Table. Comme prcdemment, je manipule mon objet avant son ajout la collection.
Collection Keys
Nous allons aborder maintenant un des passages un peu "sensibles" de la programmation ADOX, c'est dire les cls. Les cls sont le fondement de l'intgrit rfrentielle il convient de bien connatre le sujet avant de se lancer dans leur programmation. Normalement, vous avez travaill sur le modle de donnes avant de construire votre base donc il suffira de suivre celui-ci.
Quelques notions
Les cls sont parfois appeles, improprement d'ailleurs, contraintes. Ceci vient du fait qu'en SQL, 'Primary Key' est une contrainte. Pourtant si les cls peuvent tre des contraintes, toutes les contraintes ne sont pas des cls.
Cl primaire
Cette cl devrait tre compose d'une seule colonne mais ce n'est pas une obligation. Elle sert identifier chaque enregistrement de manire unique. La cl primaire n'accepte pas les valeurs NULL. 109
Beaucoup de gens utilisent un champ numrotation automatique pour dfinir la cl primaire, cela n'est en rien obligatoire. Une table n'a qu'une cl primaire.
Cl trangre
Champ de mme type/sous-type de donnes que la cl primaire, qu'elle rfrence dans la table enfant. Une table peut possder plusieurs cls trangres.
Intgrit rfrentielle
Mcanisme de vrification qui s'assure que, pour chaque valeur de cl trangre, il y a une valeur de cl primaire lui correspondant. Ce mcanisme se dclenche lors de l'ajout d'une nouvelle valeur de cl trangre, lors de la suppression d'une cl primaire ou lors de la modification de l'une quelconque des deux cls.
Opration en cascade
Mode opratoire dpendant de l'intgrit rfrentielle. Celui-ci dtermine ventuellement le comportement du SGBDR en cas de suppression/modification d'un enregistrement parent.
Mthode Append
De la forme Keys.Append Key [, KeyType] [, Column] [, RelatedTable] Nous examinerons plus tard le dtail des paramtres (dans les proprits de l'objet Key).
Objet Key
Reprsente une cl primaire, trangre ou unique.
DeleteRule
Dfinit les rgles appliquer lors de la suppression d'une valeur de la cl primaire. On distingue 4 cas :
AdRINone
Aucune action (quivalent du mode de gestion SQL ON DELETE NO ACTION). Cette valeur interdit toute suppression d'un enregistrement tant qu'il existe un enregistrement dpendant dans la base.
AdRICascade
Modifications en cascade (SQL ON DELETE CASCADE). Attention l'utilisation de cette valeur. Elle supprime tous les enregistrements lis lors de la suppression de l'enregistrement pre. Ceci peut tre trs coteux pour le SGBD en termes de performances et doit tre limit au sein d'une transaction
Name
Tel que son nom l'indique
RelatedTable
Si la cl est une cl trangre, dfinit le nom de la table contenant la cl primaire correspondante.
110
Type
Dtermine s'il s'agit d'une cl primaire, trangre ou unique. Une cl unique est une cl sans doublons.
UpdateRule
Equivalent pour les modifications DeleteRule pour les mises jour. Attention de nombreux SGBDR n'acceptent pas toutes les rgles de modification/suppression. Je vous rappelle aussi que ces rgles s'appliquent sur la cl trangre ; les dfinir pour la cl primaire ne sert rien.
Exemples
Nous allons voir maintenant un exemple de cration de cl toujours dans ma table. MaCle:=coKey.Create; with MaCle do begin Name:='Prim Cle'; type_:=adKeyPrimary; RelatedTable:=''; Columns.Append(ColId.Name,ColId.type_,ColId.DefinedSize); Columns['Id'].RelatedColumn:=''; end; MaTable.Keys.Append(MaCle,adKeyPrimary,EmptyParam,'',''); Je cre ici ma cl primaire de faon explicite mais c'est souvent plus rapide de le faire en mme temps que l'on cre l'index. Amusons nous maintenant crer une deuxime table afin de la mettre en relation. Je vais donc crer une deuxime table 'TestRelat' dans ma base. Tout d'abord dans notre premire table pour ajouter un index unique sur la colonne 'Id'. Ensuite nous allons crer une deuxime fonction de la forme. var MaTable: _table; ColCle, ColLocal, ColResp: _column; MonIndP, MonIndF: _index; MaCle: _key; begin MaTable:=coTable.Create; MaTable.Name:='TestRelation'; MaTable.ParentCatalog:=MonCat; ColCle:=coColumn.Create; ColCle.ParentCatalog:=MonCat; With ColCle do begin Name:='NumCle'; Type_:=adInteger; Properties['Nullable'].Value:=False; Properties['Autoincrement'].Value:=True; Properties['Seed'].Value:=1; Properties['Increment'].Value:=1; end; ColLocal:=coColumn.Create; ColLocal.ParentCatalog:=MonCat;
111
With ColLocal do begin Name:='Local'; Type_:=adVarWChar; DefinedSize:=50; Properties['Nullable'].Value:=False; Properties['Jet OLEDB:Allow Zero Length'].Value:=False; end; ColResp:=coColumn.Create; ColResp.ParentCatalog:=MonCat; With ColResp do begin Name:='Num_Resp'; Type_:=adInteger; Properties['Nullable'].Value:=False; end; MaTable.Columns.Append(ColCle,Unassigned,Unassigned); MaTable.Columns.Append(ColLocal,Unassigned,Unassigned); MaTable.Columns.Append(ColResp,Unassigned,Unassigned); MonIndP:=coIndex.Create; with MonIndP do begin IndexNulls:=adIndexNullsDisallow; Name:='indP'; Columns.Append(ColCle.Name,ColCle.type_,ColCle.DefinedSize); PrimaryKey:=True; Unique:=True; end; MonIndF:=coIndex.Create; with MonIndF do begin IndexNulls:=adIndexNullsDisallow; Name:='indF'; Columns.Append(ColResp.Name,ColResp.type_,ColResp.DefinedSize); end; MaTable.Indexes.Append(MonIndP,EmptyParam); MaTable.Indexes.Append(MonIndF,EmptyParam); MaCle:=coKey.Create; with MaCle do begin Name:='Etr Cle'; type_:=adKeyForeign; DeleteRule:= adRICascade; UpdateRule:= adRICascade; RelatedTable:='TestColonne'; Columns.Append(ColResp.Name, ColResp.type_, ColResp.DefinedSize); Columns['Num_Resp'].RelatedColumn:='Id'; end; MaTable.Keys.Append(MaCle,Unassigned,EmptyParam,Unassigned,Unassigne d); moncat.Tables.Append(matable); end; Donc je cre une table de trois colonnes, la troisime (num_resp) contenant la cl trangre. Le seul passage nouveau tant la cration de la cl trangre.
112
L'objet Procedure
Cet objet peut reprsenter plusieurs choses selon les cas. Une requte Action Une requte paramtre Une procdure stocke (n'existe pas dans Access) En soi cet objet est trs simple utiliser puisqu'il ne s'agit que d'un objet Command ayant un nom dans la collection Procedures. Comme nous avons vu plus haut l'objet Command suffisamment en dtail, nous allons nous pencher dans ce chapitre sur l'ajout ou la modification de l'objet Procedure.
Exemple
Dans la suite de mon exemple je peux ajouter var MaCommand:TADOCommand; begin MaCommand:=TADOCommand.Create(nil); MaCommand.CommandText:= 'PARAMETERS [QuelNom] TEXT(50);SELECT * FROM TestColonne WHERE Nom = [QuelNom]'; MonCat.Procedures.Append('Cherche par nom', MaCommand.CommandObject); end; Ce code ajoute une requte paramtre nomme 'Cherche par nom' attendant le paramtre "QuelNom" de type texte. Il est tout fait possible de crer une requte action paramtre. Par exemple PARAMETERS DateCib DateTime, BancCib Text(255);DELETE * FROM tblAnomalie WHERE NumBanc=[BancCib] AND DatDimanche>=[DateCib]; Est une requte valide.
113
Attention : Pour les paramtres, il faut bien prciser la taille dans le corps de la procdure. En effet, si j'avais cris PARAMETERS [QuelNom] TEXT, sans prciser une taille infrieure ou gale 255 j'aurais cr un paramtre de type mmo au lieu de texte.(adLongVarWChar au lieu de adVarWChar). Ce genre d'erreur engendre des bugs trs difficiles voir impossibles retrouver pour les utilisateurs de vos bases de donnes.
L'objet View
La cration et la modification d'un objet View se grent de la mme manire que pour les objets Procedure. C'est pourquoi nous ne nous rditerons pas ces notions ici. Il faut cependant savoir que la seule vraie diffrence existant entre l'Objet Procdure et l'objet View sont que ce dernier n'accepte que des requtes SELECT sans paramtre.
114
donnes ainsi qu' la structure de la base. Avec ADOX on retrouve ces concepts dans les objets User et Group.
Si vous spcifiez un fichier de scurit incorrecte, vous n'aurez pas d'erreur lors de l'ouverture du catalogue. L'erreur surviendra lors de l'appel d'un objet li la scurit. De plus vous obtiendrez une erreur 3251 " L'opration demande par l'application n'est pas prise en charge par le fournisseur." ce qui ne vous aidera pas beaucoup voir l'erreur.
Proprits et droits
Voil deux notions importantes, corrlatives et pourtant diffrentes dans leur conception.
Propritaire
Chaque objet a toujours un propritaire. A l'origine, c'est le crateur de l'objet qui est le propritaire. Le propritaire d'un objet garde le droit d'accs total (modification, suppression, droits) sur l'objet. Le propritaire d'un objet peut tre un utilisateur ou un groupe. La base de donnes est un objet un peu particulier puisque seul un utilisateur (et non un groupe) peut en tre propritaire. Le problme de la gestion de la proprit est important grer si les utilisateurs ont le droit de crer des objets. Si vous souhaitez qu'ils gardent tous les droits sur les objets qu'ils ont crs, il n'y a rien faire, mais sinon vous devez grer le transfert de la proprit. Faites toujours trs attention cette notion de propritaire, elle peut permettre des accs que vous ne souhaiteriez pas si vous n'y prenez pas garde.
Administrateur
Habituellement, l'administrateur ou les membres du groupe administrateur sont les seuls avoir tous les droits sur une base de donnes, y compris celui de modifier les droits d'accs des autres. De part ce fait, on essaye toujours de limiter le nombre d'administrateurs. Pour ce faire, il faut donc mettre au point une stratgie de droits pour que chaque groupe ait les droits ncessaires et suffisants un bon fonctionnement. L'avantage dans ce cas d'une approche par programme est la possibilit d'utiliser une connexion ayant les droits administrateurs pour permettre aux utilisateurs de faire des actions (prvues) que leurs droits ne leurs permettraient pas de faire normalement.
115
Access
Mme si cela est transparent, la scurit d'Access est toujours active. Il existe un compte "Administrateur" possdant tous les droits et n'ayant pas de mot de passe. Pour activer la scurit sur Access on procde en gnral de la manire suivante : On donne un mot de passe l'utilisateur "Administrateur". On cre un nouveau compte d'administrateur, avec un autre nom de prfrence ("Admin" par exemple) auquel on donne tous les droits et proprits On supprime le compte "Administrateur" du groupe "Administrateurs". Il n'est en effet pas possible de supprimer les Groupes et Utilisateurs par dfaut. La dernire tape n'est pas indispensable, par contre la premire l'est, car sans cela, n'importe qui ayant Access sur son poste pourra ouvrir votre base de donnes.
Utilisateurs et groupes
C'est une notion classique de la scurit des SGBD. Un groupe reprsente l'ensemble des utilisateurs possdant les mmes droits. Un utilisateur reprsente en gnrale une personne. Chaque utilisateur appartient au minimum au groupe des utilisateurs, mais il peut appartenir plusieurs groupes. Un utilisateur hrite toujours des droits du(des) groupe(s) dont il dpend, mais peut avoir des droits modifis par rapport celui-ci. Attention, en cas de contradiction entre les droits du groupe et ceux de l'utilisateur, c'est toujours la condition la moins restrictive qui l'emporte. Pour simplifier la gestion des droits, on dfinit habituellement les groupes puis les utilisateurs. Lorsqu'un groupe est propritaire, chaque utilisateur du groupe est galement propritaire. L'hritage des droits d'un groupe est paramtrable.
Objet Group
En soit un objet Group n'est pas trs complexe. Il ne possde que la collection Users qui reprsente les utilisateurs membres du groupe, une proprit Name qui est son nom et deux mthodes. C'est celles-ci que nous allons voir en dtails.
SetPermissions
Attribue les permissions sur un objet pour un groupe ou un utilisateur. De la forme
Group.SetPermissions Name, ObjectType, Action, Rights [, Inherit] [, ObjectTypeId]
Il s'agit l d'une mthode. Contrairement la valorisation des proprits ADOX qui se fait avant l'ajout de l'objet la collection, on applique les mthodes l'objet seulement aprs son ajout la collection. Passons en revue ces paramtres, mais notez bien qu'il s'appliquent aussi bien au groupe qu' l'utilisateur. Les diffrences seront vues dans l'tude de l'objet User.
ObjectType
Le paramtre ObjectType peut prendre une des valeurs suivantes : Constante Valeur Description
116
Dfini un objet spcifique du fournisseur.. Une erreur se produira si le paramtre ObjectTypeId n'est pas fourni adPermObjTable 1 Objet Table. adPermObjColumn 2 objet Column adPermObjDatabase 3 L'objet est la base de donnes adPermObjProcedure 4 Objet Procedure. AdPermObjView 5 Objet View On retrouve la tous les objets du modle plus l'objet Database. La valeur adPermObjProviderSpecific permet d'utiliser des objets spcifiques au fournisseur, par exemple pour Access les tats ou les formulaires. adPermObjProviderSpecific -1
Name
Donne le nom de l'objet cible, si vous ne prcisez pas ce nom vous devez passer NULL mais vous aurez une erreur si vous omettez le paramtre. Dans ce cas, les droits seront attribus tous les nouveaux objets de mme type contenu dans le catalogue (catgorie gnrique). Pour l'objet database, vous devez donner une chane vide ("") au paramtre Name. Voir dans l'exemple plus loin.
Action
Ce paramtre est combin avec le paramtre Rights. Il peut prendre les valeurs suivantes : Constante Valeur Description adAccessGrant 1 Le groupe aura au moins les autorisations demandes. adAccessSet 2 Le groupe aura exactement les autorisations demandes adAccessDeny 3 Le groupe n'aura pas les autorisations demandes. Tous les droits d'accs explicites que possdent le groupe seront adAccessRevoke 4 annuls. Dtaillons ces valeurs car elles dpendent fortement de la stratgie choisie ainsi que de la gestion de l'existant. Lorsque l'on considre un utilisateur, on entend par droits (autorisations) explicites, les droits qui appartiennent celui-ci et par droits implicites les droits de son groupe. Pour un groupe, tous les droits sont explicites. adAccessRevoke retire donc tous les droits du groupe, il n'est pas utile de renseigner le paramtre Rights (videmment). Le fait de retirer les droits d'un groupe ne retire pas les droits d'un utilisateur appartenant au groupe lorsqu'ils sont diffrents de ceux du groupe. adAccessDeny enlve tous les droits dfinis dans le paramtre Rights. adAccessSet est la valeur que l'on utilise en gnral lors de la cration du fichier de scurit. Les droits dfinis dans le paramtre Rights seront exactement les droits donns, ceux existant prcdemment seront remplacs ou modifis. AdAccessGrant est la valeur pour la modification de droits. Au lieu de redfinir toute la liste des droits on prcise le droit que l'on souhaite modifier, les autres droits ne seront pas redfinis.
117
Rights
Valeur de type Long reprsentant un masque binaire des droits pouvant tre une ou plusieurs des valeurs suivantes. Certaines de ces valeurs sont exclusives (adRightFull par exemple) : Constante Valeur Description Le groupe l'autorisation de crer des objets de ce adRightCreate 16384 (&H4000) type Le groupe a l'autorisation de supprimer des donnes adRightDelete 65536 (&H10000) de l'objet adRightDrop 256 (&H100) Le groupe a l'autorisation de supprimer l'objet Le groupe a l'autorisation d'accder de faon adRightExclusive 512 (&H200) exclusive l'objet 536870912 Le groupe a l'autorisation d'excuter l'objet.(macro adRightExecute (&H20000000) Access par exemple) 268435456 Le groupe a toutes les autorisations concernant adRightFull (&H10000000) l'objet Le groupe a l'autorisation d'insrer des donnes dans adRightInsert 32768 (&H8000) l'objet. adRightMaximumAllowe 33554432 Le groupe a le maximum d'autorisations spcifiques d (&H2000000) autorises par le fournisseur. Le groupe n'a aucune autorisations concernant adRightNone 0 l'objet. -2147483648 adRightRead Le groupe a l'autorisation de lire l'objet (&H80000000) Le groupe a l'autorisation de lire la dfinition de adRightReadDesign 1024 (&H400) l'objet 131072 Le groupe a l'autorisation de lire les autorisations de adRightReadPermissions (&H20000) l'objet adRightReference 8192 (&H2000) Le groupe a l'autorisation de rfrencer l'objet 1073741824 Le groupe a l'autorisation de modifier des donnes adRightUpdate (&H40000000) dans l'objet. Le groupe a l'autorisation d'accorder des adRightWithGrant 4096 (&H1000) autorisations concernant l'objet Le groupe a l'autorisation de modifier la structure de adRightWriteDesign 2048 (&H800) l'objet 524288 Le groupe a l'autorisation de modifier le propritaire adRightWriteOwner (&H80000) de l'objet 262144 Le groupe a l'autorisation de modifier des adRightWritePermissions (&H40000) autorisations spcifiques de l'objet. Comme il s'agit de masque binaire, on compose les droits avec l'oprateur "OR". Une erreur frquente consiste considrer l'attribution des droits par programmation comme fonctionnant l'identique de l'assistant scurit. Celui-ci cre implicitement des droits lorsque l'on attribue certains droits l'utilisateur (par exemple l'autorisation "modifier les donnes" donne automatiquement l'autorisation "lire les donnes"). Il n'en est pas de mme par programmation. Vous devez dfinir de faon cohrente et explicite les droits sous peine de bloquer vos utilisateurs. Une autre erreur consiste utiliser un droit qui n'existe pas pour l'objet considr. Ainsi j'ai dj vu donner le droit adRightExecute sur des procdures, srement parce que l'on dit excuter une requte, alors que c'est le droit adRightRead qu'il faut utiliser.
Inherit
Paramtre optionnel qui dtermine comment les objets contenus dans l'objet cible hriteront des autorisations. Les valeurs peuvent tre : Constante Valeur Description 118
adInheritNone 0 Valeur par dfaut. Pas d'hritage. adInheritObjects 1 Les objets non-conteneurs hritent des autorisations. adInheritContainers 2 Les objets conteneurs hritent des autorisations. adInheritBoth 3 Tous les objets hritent des autorisations. adInheritNoPropagate 4 Les objets n'ayant pas dj hrits d'autorisations hriteront. L'hritage a parfois des rsultats surprenants avec ADOX, je vous conseille donc de ne pas en abuser. Toutefois on peut toujours dterminer une configuration type par groupe et la donner la base de donnes avec un hritage, afin que les objets contenus hritent des autorisations. Je ne vous conseille pas d'utiliser cette technique.
ObjectTypeId
Ce paramtre est obligatoire avec le type adPermObjProviderSpecific. Cela permet d'accder certains objets contenus dans le SGBD. Il faut passer comme valeur le GUID de l'objet. Par exemple pour Access les GUIDs suivants permettent de dfinir des autorisations pour les formulaires, tats et macro Object GUID Form {c49c842e-9dcb-11d1-9f0a-00c04fc2c2e0} Report {c49c8430-9dcb-11d1-9f0a-00c04fc2c2e0} Macro {c49c842f-9dcb-11d1-9f0a-00c04fc2c2e0}
GetPermissions
Permet de lire les autorisations d'accs d'un objet. Ce n'est pas toujours trs simple interprter par programmation, aussi je vous conseille plutt de supprimer puis de recrer les autorisations. De la forme ReturnValue = Group.GetPermissions(Name, ObjectType [, ObjectTypeId]) Ou ReturnValue est une valeur de type Long reprsentant le masque binaire. Pour tester si une autorisations existe dans le masque utiliser l'oprateur AND La fonction suivante permet d'afficher les droits dans la fentre d'excution. var MaTable: _table; MonGroup: _Group; Droits: integer; begin MonCat:=coCatalog.Create; moncat.Set_ActiveConnection('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\tutoriel\biblio.mdb;Jet OLEDB:System database=c:\tutoriel\system.mdw;User Id=Admin; Password='); Mongroup:=coGroup.Create; mongroup:=moncat.Groups.Item[1]; Droits:=MonGroup.GetPermissions('Authors',adPermObjTable,EmptyParam) ; If Bool(Droits And adRightCreate) Then showmessage('Cration'); If Bool(Droits And adRightDelete) Then showmessage('Effacement'); If Bool(Droits And adRightDrop) Then showmessage('Suppression'); If Bool(Droits And adRightExclusive) Then showmessage('Exclusif'); If Bool(Droits And adRightExecute) Then showmessage('Excution'); If Bool(Droits And adRightInsert) Then showmessage('Insertion');
119
If Bool(Droits And adRightRead) Then showmessage('Lecture D'); If Bool(Droits And adRightReadDesign) Then showmessage('Lecture S'); If Bool(Droits And adRightReadPermissions) Then showmessage('LirePermission'); If Bool(Droits And adRightReference) Then showmessage('Reference'); If Bool(Droits And adRightUpdate) Then showmessage('Modification'); If Bool(Droits And adRightWithGrant) Then showmessage('ModifPermission'); If Bool(Droits And adRightWriteDesign) Then showmessage('Modif Structure'); If Bool(Droits And adRightWriteOwner) Then showmessage('Modif Prop'); end;
Objet User
Cet objet ressemble fortement l'objet Group dans sa programmation. Il possde une mthode de plus.
ChangePassword
Permet de modifier le mot de passe d'un utilisateur. User.ChangePassword OldPassword, NewPassword L pas besoin d'explication si ce n'est qu'un des paramtres peut tre la chane vide ""
Properties
Les objets User et Group ont une collection properties.
Exemple
Dans l'exemple suivant nous allons scuriser une base de donnes, crer deux groupes et deux utilisateurs. En phase de cration de base, commencez toujours par grer la scurit comme vous allez le voir c'est beaucoup plus simple. var MonGroup: _Group; MonUser: _User; begin MonCat:=coCatalog.Create; moncat.Create('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\tutoriel\secu.mdb;Jet OLEDB:System database=c:\tutoriel\system.mdw;User Id=Admin; Password='); Mongroup:=coGroup.Create; MonCat.Users.Item['Admin'].ChangePassword('','password'); Moncat.Groups.Append('Utilisateurs');
120
Mongroup:=MonCat.Groups.Item['Utilisateurs']; with MonGroup do begin SetPermissions(Null, adPermObjTable, adAccessSet, adRightInsert Or adRightRead Or adRightUpdate, adInheritNone,EmptyParam); SetPermissions(Null, adPermObjProcedure, adAccessSet, adRightRead Or adRightReadDesign, adInheritNone,EmptyParam); SetPermissions(Null, adPermObjView, adAccessSet, adRightRead Or adRightReadDesign, adInheritNone,EmptyParam); end; Moncat.Groups.Append('Consultants'); Mongroup:=MonCat.Groups.Item['Consultants']; with MonGroup do begin SetPermissions(Null, adPermObjTable, adAccessSet, adRightNone, adInheritNone,EmptyParam); SetPermissions(Null, adPermObjProcedure, adAccessSet, adRightNone, adInheritNone,EmptyParam); SetPermissions(Null, adPermObjView, adAccessSet, adRightRead, adInheritNone,EmptyParam); end; MonCat.Users.Append('Emile',''); MonCat.Users.Item['Emile'].ChangePassword('','xtf22re'); MonCat.Groups.Item['Utilisateurs'].Users.Append('Emile',unassigned); MonUser:= MonCat.Users.Item['Emile']; with MonUser do begin SetPermissions(Null, adPermObjTable, adAccessSet, adRightInsert Or adRightRead Or adRightUpdate, adInheritNone,EmptyParam); SetPermissions(Null, adPermObjProcedure, adAccessSet, adRightRead Or adRightReadDesign, adInheritNone,EmptyParam); SetPermissions(Null, adPermObjView, adAccessSet, adRightRead Or adRightReadDesign, adInheritNone,EmptyParam); end; end; Comme nous le voyons, la premire tape aprs la cration est de changer le mot de passe de l'administrateur afin d'activer la scurit. Je cre ensuite deux groupes dont je dfinis les droits puis deux utilisateurs (1 par groupe). Notez que je redfinis les mmes droits puisque ADO avec Access ne gnre pas l'hritage implicite des droits du groupe. Dans ce cas, je n'ai encore cr aucun objet dans ma table. Ceci me permet d'utiliser des catgories gnriques pour l'attribution des droits (utilisation de Null pour le paramtre Name). Vous remarquerez enfin que j'utilise SetPermissions aprs l'ajout de l'objet sa collection
Techniques de scurisation
Il est bien vident que grer la scurit par le code n'est pas une sincure, aussi on essaye toujours de passer par les assistants ad hoc des SGBD plutt que de se lancer dans un code aventureux. Dans le cas d'Access, il est beaucoup plus parlant de grer les scurits depuis Access. Si toutefois vous devez utiliser le code, vous gagnerez en efficacit et en simplicit suivre le modle DAO pour grer la scurit, du moins tant que Microsoft n'aura pas sorti une version ADO faisant cela efficacement. Nous allons partir du principe dans cette partie que l'emploi du code avec ADO est indispensable. Grosso modo il n'existe que deux cas, soit vous allez modifier des groupes, des utilisateurs ou des autorisations existantes soit il vous faut tout crer. 121
Modification
Si la scurit est dj bien gre, il ne s'agit que d'ajouter des utilisateurs et de bien grer les droits d'accs ou de proprit. La technique consiste toujours crer un utilisateur dans un groupe existant, afin de ne pas avoir grer tous ses droits objet par objet. Lors de la modification d'autorisations, il est souvent plus simple de dtruire les anciennes, puis de r attribuer les autorisations. Cherchez toujours donner la proprit des nouveaux objets l'administrateur ou alors crez un utilisateur fantme, appels par exemple "Code", qui possde les autorisations ncessaires tous vos accs par le code. Si la scurit est mal gre, dtruisez tout et repartez du dbut. N'essayez pas de prendre la proprit des tables dont le propritaire est "engine" (information de schma du moteur Jet) vous dclencheriez une erreur.
Cration
Lors de la cration de la base, je vous conseille vivement de crer la scurit en premier. Vous pouvez ds lors utiliser des objets gnriques et dfinir vos droits assez rapidement. Il vaut toujours mieux avoir un groupe de trop que l'on supprime la fin, plutt que de se rendre compte qu'il en manque un. Dfinissez votre systme de scurit avant de coder. Par le code il vaut mieux multiplier les groupes que les utilisateurs.
CONCLUSION
Vous voil dsormais matrisant ADO tel Cochise son tomahawk. Les quelques rgles toujours garder en mmoire sont : Bien choisir son curseur Privilgiez une approche Delphi plutt qu'ADO pur. Utilisez le SQL quand le fournisseur cible est unique. Si vous rencontrez des problmes n'hsitez pas venir poser vos questions sur Developpez.com : Delphi et base de donnes Bonne programmation.
122