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

Comprendre les Recordset ADO

Comprendre les Recordset ADO


précédentsommairesuivant

V. Les informations de schéma

Elles se trouvent dans la collection Properties de l'objet Field. Elles existent toujours quel que soit le côté du curseur, mais il y en a plus du côté 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.

V-A. Taille du cache

Régler 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 données, celui-ci n'est pas forcément complètement rapatrié dans l'espace client. Vous pouvez définir le nombre d'enregistrements qui seront transmis à chaque fois en réglant la propriété CacheSize de l'objet Recordset. Ce réglage ce fait en donnant le nombre d'enregistrements que le cache peut accepter dans la propriété CacheSize. S'il y a plus d'enregistrements retournés qu'il n'y a de place dans le cache, le fournisseur transmet uniquement la quantité d'enregistrements nécessaire 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 piège. Lors des rafraîchissements périodiques de votre recordset (dynamique ou KeySet), les enregistrements du cache ne sont pas rafraîchis. Il faut forcer celui-ci en appelant la méthode Resync.

Il n'y a pas de règles absolues pour dire qu'elle taille de cache il faut utiliser. Je n'ai pour ma part pas trouvé d'autres méthodes que de faire des tests selon les applications.

V-B. Marshaling

Les objets ADO suivent les règles du marshaling COM pour la communication inter-process. Le marshaling (ou re-direction) représente la méthode de communication des objets ou/et des données au-delà des limites d'un thread ou d'un processus. Dans le cas de la communication in-process, ADO utilise la gestion de threads libres (non cloisonnés), il n'y a donc jamais de marshaling in-process avec les recordset ADO. C'est le cas par exemple avec des applications mono poste utilisant des SGBD qui ne sont pas Client / Serveur ou des sources de données particulières.

Dans tous les autres cas, il y a toujours marshaling. Néanmoins, il y a de profondes différences selon la position du curseur. Dans le principe, lorsque vous créez un recordset, celui-ci est construit dans l'espace du serveur. Il est ensuite transmis au process client. Si vous êtes côté serveur, la transmission se fait par référence et vous manipulez un pointeur vers le curseur qui se trouve dans le process du serveur. En ce sens, vous n'avez pas à vous préoccuper de comment les modifications du recordset sont répercutées sur la source de données. Seuls les enregistrements présents dans le cache sont effectivement transmis à l'espace client.

Par contre, lors de l'utilisation d'un curseur client, le Recordset est transmis par valeur, c'est à dire qu'une copie est créée dans l'application cliente (données et méta-données). Les informations de connexions ne sont pas transmises avec les données puisque vous les avez définies lors de la création de l'objet recordset. De cette observation découle deux concepts fondamentaux des curseurs clients :

  • Comme le transfert peut être long, il ne serait pas sans risque de créer un recordset "dynamique" puisque le rafraîchissement périodique pourrait débuter avant la récupération complète du jeu d'enregistrement. Ainsi ADO ne vous fournira que des curseurs statiques côté client, quel que soit le fournisseur. Une fois le rapatriement effectué, le recordset n'est plus connecté, dans le sens ou tous les appels ultérieurs à la source de données seront explicites. Une fois le jeu d'enregistrement transmis au processus client, l'accès aux données va être très rapide.
  • Pour mettre à jour des données vers la source, il va falloir re marshaler le recordset vers le processus du serveur. La communication d'un curseur client avec la source de données est toujours assez lente.

Pour en finir avec le marshaling ADO, sachez que le recordset et sa connexion doivent être dans le même processus.

V-C. Mécanismes de base

Nous avons maintenant le minimum requis pour comprendre ce qui se passe lors de la manipulation d'un objet Recordset. Je rappel ici ce point fondamental, que nous reverront en détails dans les rappels ADO, l'utilisation des propriétés dynamiques des objets ADO demandent toujours la définition du fournisseur. En clair, la propriété Provider doit être définie pour utiliser les propriétés dynamiques de l'objet Connection, et la propriété ActiveConnection doit être valorisée pour utiliser les collections Properties des objets Command et Recordset.

Nous allons regarder maintenant quelques opérations 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 côté serveur est géré par le fournisseur de données.

Création du jeu d'enregistrement

Généralement, pour ouvrir un Recordset, quelle que soit la méthode utilisée, vous allez consciemment ou non envoyer une requête SQL SELECT au fournisseur de données. Il n'est nullement nécessaire d'avoir la moindre connaissance de la structure de la base, pour peu que celle-ci possède des requêtes stockées (Vues), néanmoins on connaît en général le schéma de la source (au moins superficiellement). Fondamentalement, les différences de fonctionnement commencent lors de l'appel de la méthode open, mais souvent pour des raisons de marshaling, tout se passe dès le paramétrage de l'objet. Examinons le code suivant :

 
Sélectionnez
Set Cnn1 = New ADODB.Connection
Cnn1.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Mes documents\jmarc\ADO\bdd\biblio.mdb ;User Id=Admin; Password="
Set MonRs = New ADODB.Recordset
MonRs.CursorLocation = adUseClient
MonRs.ActiveConnection = Cnn1
MonRs.Source = "SELECT * FROM Authors"
MonRs.Open , , adOpenStatic, adLockBatchOptimistic, adCmdText

Si j'espionne le recordset avant l'appel de la méthode Open, je vois que quelques propriétés dynamiques (informations de schéma) et toutes les propriétés statiques (méta-données) des objets Field du recordset sont correctement valorisées quelle que soit la position du curseur. La raison est simple, ADO a envoyé une requête de récupération de schéma lors de l'affectation de la propriété source. Si vous avez bien compris ce que nous avons vu précédemment, vous voyez déjà apparaître un des problèmes des curseurs clients. En effet, avec un curseur côté serveur, la requête de schéma valorise les méta-données de l'objet recordset (serveur) puis un pointeur d'interface est marshalé vers l'application cliente. Dans le cas du curseur client, le moteur de curseur marshale les méta-données vers l'objet Recordset qui est situé dans l'espace client. Il y une différence importante de temps de traitement entre les deux, alors que le volume des méta-données transmis est très faible. Il va y avoir ensuite une différence encore plus importante lors de l'appel de la méthode Open.

Côté serveur, le fournisseur exécute la requête et transmet les enregistrements nécessaires au remplissage du cache, pas besoin de re transmettre un pointeur puisque celui-ci existe déjà. Ce traitement peut être encore accéléré avec un curseur à jeu de clé (Key set) puisque celui ci ne valorise qu'un jeu de clé.

Le moteur de curseur côté client va par contre devoir récupérer les valeurs de la source renvoyées par la requête et les marshaler vers le recordset de l'espace client. Ceci étant fait, comme le paramétrage précise que le Recordset doit être modifiable (Updatable), il va renvoyer une requête de schéma, évidemment différente de la première, afin de récupérer les informations nécessaires aux actions éventuelles sur la source de données.

Comme nous le voyons, la construction d'un recordset client est assez lourde, surtout si la requête doit renvoyer des milliers d'enregistrements.

Action sur le recordset

Que va-t-il se passer lorsqu'on fait une modification sur l'objet recordset ?

Pour bien comprendre l'influence de la position du curseur je vais imaginer une table sans clé, contenant des doublons parfaits (lignes dont toutes les valeurs sont identiques).

Exécutons le code suivant

 
Sélectionnez
Dim MaCnn As ADODB.Connection, MonRs As ADODB.Recordset

Set MaCnn = New ADODB.Connection
MaCnn.Open "Provider=Microsoft.Jet.OLEDB.4.0 ;Data Source=D:\user\jmarc\bd6.mdb ;User Id=Admin; Password="
Set MonRs = New ADODB.Recordset
MonRs.CursorLocation = adUseServer
MonRs.Open "SELECT * From TabSansCle", MaCnn, adOpenKeyset, adLockOptimistic, adCmdText
MonRs.Find "nom = 'bb'"
MonRs!numer = 21
MonRs.Update

Dans ma table j'ai plusieurs enregistrements dont le nom est "bb", certains d'entre eux étant des doublons parfaits. Si je regarde ma base après exécution, je constate qu'un de mes enregistrements a pris 21 pour valeur "numer".

Maintenant j'exécute le même code en positionnant côté client le recordset. A l'appel de la méthode Update, j'obtiens l'erreur "80004005 : Informations sur la colonne clé insuffisantes ou incorrectes ".

Ce petit exemple permet de démontrer la différence de fonctionnement entre les curseurs clients et serveurs. Dans le cas de l'opération côté serveur, c'est le fournisseur qui gère le curseur. Le fournisseur sait localiser physiquement l'enregistrement en cours du recordset dans la source de données et peut donc procéder à la modification.

Par contre, lorsqu'on procède du côté client, le moteur de curseur doit écrire une requête action pour le Fournisseur. Dans notre cas, le curseur ne trouve pas dans les méta-données de clé primaire pour la table (puisque la table n'en contient pas) ni d'index unique. Il se trouve donc dans l'impossibilité d'écrire une requête action pertinente, puisqu'il ne peut pas identifier l'enregistrement en cours, ce qui provoque la génération de l'erreur dont pour une fois le message est tout à fait explicite. Dans ce cas précis, le curseur porte mal son nom puisqu'il existe un enregistrement en cours dans le recordset, mais qu'il ne peut pas être identifié pour agir dessus. Néanmoins j'ai pris là un cas extrême, à la limite d'être absurde.

Dans le cas des recordset côté client, il y a toujours tentative de construction d'une requête action pour traduire les modifications apportées à l'objet Recordset.

Echec ou réussite

Il est important de comprendre comment le curseur va réagir lors de l'échec ou de la réussite d'une action. Le moteur de curseur (client ou serveur) utilise les méta-données stockées dans le recordset pour valider une opération sur un recordset. Ce contrôle à lieu en deux temps :

  • Lors de l'affectation d'une valeur à un champ

La valeur saisie doit respecter les propriétés 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 même message selon la position du curseur. Si par exemple vous tentez d'affecter une valeur de type erroné à un champ, vus recevrez un message d'incompatibilité de type pour un curseur Serveur, et une erreur Automation pour un curseur client.

  • Lors de la demande de mise à jour

Indépendamment de la position lorsqu'il y a un échec, une erreur standard est levée et il y a ajout d'une erreur à la connexion défini dans la propriété ActiveConnection de l'objet Recordset. La propriété Status de l'enregistrement concerné prendra une valeur censée refléter la cause de l'échec. Là encore, la pertinence de cette valeur va varier selon la position du curseur. Dans une opération côté serveur, si dans un accès avec verrouillage optimiste je cherche à modifier une valeur d'un enregistrement qui vient d'être modifiée par un autre utilisateur, j'obtiendrais une erreur "signet invalide" puisque le numéro de version ne sera plus le bon. Dans la même opération côté client, il y aura une erreur "violation de concurrence optimiste".

Le pourquoi de ces deux erreurs différentes pour une même cause va nous amener à comprendre comment le moteur de curseur du client construit ses actions.

En cas de réussite de la modification, l'opération est marquée comme réussie (dans la propriété Status) et il y a alors synchronisation.

Synchronisation

La synchronisation est l'opération qui permet de mettre à jour le recordset avec les données de la source de données sous-jacente. La synchronisation est normalement explicite, c'est à dire demandée par l'utilisateur à l'aide de la méthode Resync, mais elle suit aussi une opération de modification des données, elle est alors implicite.

Synchronisation explicite
  • Elle peut porter sur tout ou partie d'un recordset ou sur un champ. Il existe deux stratégies différentes selon que l'on désire rafraîchir l'ensemble du recordset ou juste les valeurs sous-jacentes. Dans le premier cas, toutes les modifications en cours sont perdues, dans l'autre on peut anticiper la présence de conflit.
Synchronisation de modification
  • Elle est plus complexe à appréhender. Elle suit les règles suivantes :
    • Elle porte uniquement sur les enregistrements modifiés dans votre recordset
    • Les champs sur lesquels la valeur a été modifiée sont toujours synchronisés
  • Sur un recordset côté serveur, la synchronisation qui suit une opération sur les données et toujours automatique et non paramétrable. Elle porte sur tous les champs de l'enregistrement modifié.
  • Par contre du côté client il est possible de spécifier le mode de synchronisation à l'aide de la propriété dynamique "Update Resync".

N'oubliez pas la règle d'or, les champs modifiés d'une opération réussie mettent la nouvelle valeur de la base dans leur propriété OriginalValue.

V-D. Modification d'un Recordset client

Ce que nous allons voir maintenant est propre aux curseurs côté client. Rappelez-vous que la gestion des jeux d'enregistrements côté serveurs est faite par le fournisseur de données et qu'il n'est donc pas la peine de se pencher sur ce fonctionnement en détail.

Lors d'une modification des données de l'objet Recordset, le moteur de curseur va construire une ou plusieurs requêtes action permettant de transmettre les modifications de l'objet Recordset vers la source de données. En SQL, il n'existe que trois types de requêtes action pour manipuler les données, de même, il n'existe que trois méthodes de modification des données d'un recordset. Dans la suite de ce chapitre, nous regarderons l'application de traitement direct, les traitements par lots reposant sur le même principe (en ce qui concerne les requêtes SQL).

Si vous êtes étanche au SQL, le passage qui suit va vous sembler abscons. Je vous engage alors à commencer par consulter l'excellent cours de SQLPRO.

Modification (Update)

Commençons par un exemple simple.

 
Sélectionnez
Set Cnn1 = New ADODB.Connection
Cnn1.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Mes documents\jmarc\ADO\bdd\biblio.mdb ;User Id=Admin; Password="
Set MonRs = New ADODB.Recordset
MonRs.CursorLocation = adUseClient
MonRs.ActiveConnection = Cnn1
MonRs.Open "SELECT * FROM Authors", , adOpenStatic, adLockBatchOptimistic, adCmdText
MonRs.Find "Author = 'Gane, Chris'", , adSearchForward, 1
MonRs![year born].Value = "1941"
MonRs.Update

Lors de l'appel de la méthode Update, le moteur de curseur va construire une requête SQL UPDATE pour appliquer la modification. Par défaut le modèle de la requête est de la forme

 
Sélectionnez
UPDATE Table 
SET Champs modifiés=recodset.fields(Champs modifiés).Value
WHERE champs clés= recodset.fields(Champs clés).OriginalValue
AND champs modifiés= recodset.fields(Champs modifiés).OriginalValue
AND éventuelle condition de jointure

Donc dans l'exemple que j'ai pris, la requête transmise par le moteur de curseur au fournisseur OLE DB sera :

 
Sélectionnez
UPDATE Author
SET [year born] = 1941
WHERE  Au_Id = 8139
	AND [year born] = 1938

Comment le moteur de curseur fait-il pour construire cette requête ?

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 récupérer dans les méta-données la clé primaire de la table. Avec ces informations, il va construire sa requête sachant que les valeurs utilisées dans la clause WHERE sont toujours tirées des propriétés OriginalValue des objets Fields de l'enregistrement en cours.

Si d'aventure vous n'avez pas inclus le(s) champ(s) constituant la clé primaire, il est possible que le moteur ait quand même récupéré cette information. Si tel n'est pas le cas ou si votre table ne possède pas de clé primaire, le moteur lancera une requête d'information pour trouver un éventuel index unique lui permettant d'identifier de manière certaine l'enregistrement courrant. Si cela s'avère 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 nécessaires au moteur de curseur si on désire effectuer des modifications de données.

Cette requête est ensuite transmise au fournisseur OLE DB. Celui-ci retourne en réponse au moteur de curseur le nombre d'enregistrements affectés. Si celui ci est égal à zéro, il y a déclenchement d'une erreur, sinon l'opération est marquée comme réussie. 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 numéro de version du verrouillage optimiste que la requête échoue mais parce que les valeurs stockées dans les propriétés OriginalValue ne permettent plus d'identifier l'enregistrement en cours. Or cela peut être très différent. Imaginons le scénario suivant, dans le temps entre la création du recordset et l'appel de la méthode 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 réussir, puisque le champ 'Author' n'apparaît pas dans la clause WHERE.

Pour éviter ce problème ou pour contourner d'autres limitations, on peut paramétrer les règles de construction de la requête action du moteur de curseur à l'aide de la propriété dynamique "Update Criteria".

Il y a aussi un danger si on fait l'amalgame entre ADO et SQL. Avec une requête SQL de modification je peux m'affranchir de la valeur originelle du champ. Par exemple la requête SQL suivante est valide :

 
Sélectionnez
UPDATE Author
SET [year born] = [year born] + 1
WHERE  Au_Id = 8139

Mais du fait de la concurrence optimiste elle ne peut pas être exécutée par le moteur de curseur ADO avec un code comme celui ci-dessous :

 
Sélectionnez
MonRs.Find "Author = 'Gane, Chris'", , adSearchForward, 1
MonRs![year born].Value = MonRs![year born].Value + 1
MonRs.Update

Ce code ne fonctionnera que si la valeur du champ [year born] dans la base est la même que celle présente

Suppression (DELETE)

Ce sont ces requêtes qui posent le moins de problèmes avec ADO. Dans ce cas, la requête n'utilisera que le(s) champ(s) composant la clé primaire pour écrire la requête Delete SQL.

 
Sélectionnez
MonRs.Find "Author = 'Gane, Chris'", , adSearchForward, 1
MonRs.Delete adAffectCurrent

Sera transformé par le moteur en :

 
Sélectionnez
DELETE Authors WHERE Au_Id = 8139

Là pas de problème de critère ou de synchronisation.

Ajout (INSERT INTO)

Ces requêtes vont nous permettre de comprendre le principe de la synchronisation. Une remarque s'impose au préalable. Ne tentez pas d'entrer une valeur dans un champ à valeur automatique (NumeroAuto ou TimeStamp), vous obtiendrez alors une erreur systématique. De même, donnez des valeurs à tous les champs ne pouvant être NULL avant d'appeler la méthode Update. Prenons un exemple :

 
Sélectionnez
Dim Cnn1 As ADODB.Connection, MonRs As ADODB.Recordset, ValCle As Long

Set Cnn1 = New ADODB.Connection
Cnn1.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Mes documents\jmarc\ADO\bdd\biblio.mdb ;User Id=Admin; Password="
Set MonRs = New ADODB.Recordset
MonRs.CursorLocation = adUseClient
MonRs.ActiveConnection = Cnn1
MonRs.Open "SELECT * FROM Authors", , adOpenStatic, adLockOptimistic, adCmdText
'MonRs.Properties("Update Resync") = adResyncAutoIncrement
With MonRs
    .AddNew
    .Fields("Author") = "RABILLOUD, Jean-Marc"
    .Fields("year born") = 1967
    .Update
End With

Comme vous le voyez, je n'entre pas de valeur pour le champ "Au_Id" puisqu'il s'agit d'un champ NumeroAuto. La requête action écrite par le moteur de curseur sera :

 
Sélectionnez
INSERT INTO Authors (Author, [year born])
VALUES ("RABILLOUD , Jean-Marc",1967)

Comme vous le voyez, la requête ne contient pas de valeur pour le champ "Au_Id" celle-ci étant attribuée par le SGBD. Jusque là pas de problème. Mais que va-t-il se passer si j'ai besoin de connaître cette valeur dans la suite de mon code.

Il me suffit d'écrire :

 
Sélectionnez
ValCle = MonRs.Fields("AU_Id").Value

Bien que je n'aie pas spécifié le mode de mise à jour (ligne en commentaire), la valeur par défaut de synchronisation est "adResyncAutoIncrement" et le code fonctionne. Attention, cela ne fonctionnera pas avec une version d'ADO inférieure à 2.1 ni avec un fournisseur tel que JET 3.51.

Cela fonctionne car le moteur de curseur envoie des requêtes complémentaires afin de mettre à jour le nouvel enregistrement. Cela ne fonctionnera pas avec Jet 3.51 car il ne supporte pas la requête SELECT @@IDENTITY qui permet de connaître le nouveau numéro attribué, et sans celui-ci il n'est pas possible pour le moteur de curseur d'identifier l'enregistrement nouvellement créé.

Pour connaître les possibilités de paramétrage de la mise à jour, allez lire le paragraphe "Update Resync".

Requête avec jointure

Jusque là, nous avons regardé des cas simples ne portant que sur une seule table, mais dans les autres cas, cela peut être nettement plus compliqué.

Imaginons la requête suivante :

 
Sélectionnez
SELECT * FROM Publishers, Titles WHERE Publishers.PubID = Titles.PubID

Note SQL : Il est bien évidemment plus logique d'écrire une requête avec INNER JOIN, mais dans ce chapitre je vais faire mes jointures dans la clause WHERE afin de garder une écriture cohérente avec le moteur de curseur.

J'utilise le code suivant :

 
Sélectionnez
Dim Cnn1 As ADODB.Connection, MonRs As ADODB.Recordset

Set Cnn1 = New ADODB.Connection
Cnn1.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=d:\biblio.mdb ;User Id=Admin; Password=" 
Set MonRs = New ADODB.Recordset
With MonRs
    .CursorLocation = adUseClient
    .ActiveConnection = Cnn1
    .Open "SELECT * FROM Publishers, Titles WHERE Publishers.PubID = Titles.PubID", , adOpenStatic, adLockOptimistic, adCmdText
    .Find "Title='Evaluating Practice'"
    .Fields("Name") = "ALPHA BOOKS"
    .Fields("Title") = .Fields("Title") & "s"
    .Update
End With

Pour résumer, 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, vu comme cela, la technique a l'air un peu branlante, mais c'est exactement ce que 95% des utilisateurs essayent de faire de manière transparente avec un Datagrid par exemple. Que va-t-il se passer si vous exécutez ce code ?

Le moteur de curseur va écrire autant de Requête action que de tables concernées par les modifications dans notre cas :

 
Sélectionnez
UPDATE Publishers
SET Name =  'ALPHA BOOKS'
WHERE  PubId = 129 AND Name = 'ALLYN & BACON'

Et

 
Sélectionnez
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é étrangère pour obtenir ce que nous voulons mais nous voyons que le moteur de curseur ne peut pas gérer ce style de programmation.

Dans ce cas pourtant, il existe des propriétés dynamiques qui permettent de contourner le problème. En effet vous pouvez définir à l'aide de la propriété "Unique Table", une table sur laquelle porteront les modifications dans une requête avec jointure, et à l'aide de la propriété "Resync Command" un mode de synchronisation pour ces mêmes requêtes. Pour voir leur utilisation, lisez l'exemple "propriétés dynamiques - Resync Command" du présent cours.

V-E. FireHose, un curseur particulier

Par défaut, le curseur fourni est un curseur 'FireHose' qui a les caractéristiques suivantes :
  • Côté serveur
  • En avant seulement
  • Lecture seule
  • Taille du cache=1

Ce type de curseur est le plus rapide. Il possède une particularité dont il convient de se méfier. La connexion utilisée par un tel curseur est exclusive, ce qui revient à dire que la création d'un autre recordset utilisant la même connexion engendre la création d'une connexion implicite. Encore pire, si vous fermez votre recordset avant d'avoir atteint la position EOF, la connexion sera toujours considérée comme étant utilisée. Il convient donc de faire attention avec ce type de curseur.

Attention bien que la méthode MoveLast provoque un mouvement vers l'avant, elle déclenche une erreur sur les curseurs "En avant seulement".

V-F. Conseils pour choisir son curseur

En suivant les règles données ci-dessous vous ne devriez pas vous tromper souvent. Néanmoins ces règles ne sont pas intangibles et dans certains cas il vous faudra procéder à votre propre analyse pour déterminer le curseur nécessaire.

  • Certaines propriétés/méthodes ne fonctionnent qu'avec un curseur d'un côté spécifique, si vous avez besoin de celles-ci, utilisez le curseur correspondant.
  • Les fournisseurs ne donnent pas accès à toutes les combinaisons possibles. Etudiez d'abord les curseurs que le fournisseur peut mettre à disposition
  • Sur une application ou le SGBD est sur la machine du client et qui ne peut avoir qu'un utilisateur simultané, on utilise toujours un curseur côté serveur.
  • Si vous devez modifier des données (mise à jour, ajout, suppression) utilisez un curseur côté serveur.
  • Si vous devez manipuler un gros jeu d'enregistrement, utilisez un curseur côté client.
  • Privilégiez toujours
    • Les curseurs en avant s'ils sont suffisants
    • Les curseurs en lecture seule
  • Pour les applications Multi-Utilisateurs
    • Verrouillez en mode pessimiste les curseurs côté serveur
    • Travaillez par lot sur les curseurs côté clients
  • Préférez toujours l'utilisation du SQL par rapport aux fonctionnalités du recordset.

V-G. Synchrone Vs Asynchrone

Avec ADO, on peut travailler de manière synchrone ou asynchrone. La programmation asynchrone présente évidemment l'avantage de ne pas bloquer l'application en cas de travaux longs du fournisseur mais demande de gérer une programmation évènementielle spécifique. En fait, certains évènements ADO se produiront quel que soit le mode choisi, d'autres ne seront utilisés qu'en mode asynchrone.

On peut travailler avec des connexions asynchrones et/ou des commandes asynchrones. Pour travailler de manière asynchrone vous devez utiliser un objet Connection (pas de connection implicite). Le risque avec le travail asynchrone réside dans la possibilité de faire des erreurs sur des actions dites "bloquantes".

V-H. Opération globale (par lot)

Les opérations globales sont une suite d'instructions affectant plusieurs enregistrements ou manipulant la structure. Bien que l'on puisse y mettre n'importe qu'elle type d'instructions, on essaye de grouper des opérations connexes dans une opération par lot. Trois types d'opérations globales sont généralement connus.

V-H-1. Les transactions

La plupart des SGBDR gèrent le concept de transaction (on peut le vérifier en allant lire la valeur de Connection.Properties("Transaction DDL")).

Une transaction doit toujours respecter les règles suivantes :
  • Atomicité : Soit toutes les modifications réussissent, soit toutes échouent
  • Cohérence : La transaction doit respecter l'ensemble des règles d'intégrité
  • Isolation : Deux transactions ne peuvent avoir lieu en même temps sur les mêmes données
  • Durabilité : Les effets d'une transaction sont définitifs. Par contre toute transaction interrompue est globalement annulée.

Avec ADO, les transactions s'utilisent sur l'objet Connection

V-H-2. Les procédures stockées

Procédure écrite dans le SGBD qui fait l'opération. Elle présente de multiples avantages.
  • Pas de trafic réseau
  • Environnement sécurisé
  • Pas de code complexe pour l'utiliser

Par contre, il faut qu'elle soit prévue dans la base ce qui est loin d'être toujours le cas.

Avec ADO elle s'utilise avec l'objet Command.

V-H-3. Gérée par le code

C'est le cas qui nous intéresse ici. Vous devez en principe, dans votre code, suivre les mêmes contraintes que pour une transaction. Pour ne pas rencontrer trop de problème je vous conseille d'écrire des opérations par lot assez petites.

Pour respecter ces règles vous devez vous assurer par le code que :
  • La procédure ne démarre pas si un des enregistrements cible à un verrou
  • Si une erreur ne peut pas être résolue pendant le traitement, tout doit être annulé
  • Si une erreur provient d'une violation d'intégrité, tout doit être annulé
  • Entre le démarrage et la fin de la procédure, les enregistrements concernés doivent être verrouillés.

Ces règles ne sont pas inviolables, l'essentiel est de bien vérifier que les modifications ne laissent pas la base dans un état instable.

V-I. Le piège "l'exemple Jet"

Nous avons vu que lorsque vous paramétrez votre Recordset vous émettez un souhait de curseur. Rien ne vous dit que ce curseur existe ou est géré par le fournisseur, et qui pis est, rien ne vous le dira. En effet, le fournisseur cherche à vous fournir le curseur demandé, s'il ne l'a pas, il vous donnera un curseur s'approchant le plus, mais ne vous enverra jamais de messages d'erreurs. C'est là que le nid à bugs est dissimulé.

Selon les fournisseurs, la différence entre ce que vous désirez et ce que vous aurez peut être énorme. Cela explique la plupart des anomalies que vous constatez lors de l'exécution de votre code.

Nous allons voir tous les problèmes que cela peut poser en étudiant le cas des bases Access avec Microsoft Jet 4.0.

  • Il n'est pas possible d'obtenir un curseur dynamique (2) avec JET.
  • Les curseurs côté serveur sont soit :
    • En lecture seule avec tout type de curseur (sauf dynamique évidemment)
    • De type KeySet avec tout type de verrouillage
  • Les curseurs côté clients sont :
    • Toujours statiques et ne peuvent avoir un verrouillage pessimiste

Comme vous le voyez, voilà qui réduit fortement le choix de curseurs disponible. Quelles que soient les options choisies, vous aurez un des curseurs donnés ci-dessus.

Vous aurez par contre toujours un curseur du côté choisi. Evidemment, le problème est équivalent lorsque vous utilisez un contrôle de données.

Ce petit tableau vous donnera le résumé de ce qui est disponible.

CursorLocation CursorType LockType
adUserServer AdOpenForwardOnly AdOpenKeyset adOpenStatic AdLockReadOnly
AdOpenKeyset AdLockReadOnly AdLockPessimistic AdLockOptimistic AdLockBatchOptimistic
adUseClient adOpenStatic AdLockReadOnly AdLockOptimistic AdLockBatchOptimistic

V-J. Recordset Vs SQL

Beaucoup de développeurs expérimentés préfèrent utiliser des requêtes actions et du code SQL dès qu'il s'agit de modifier des données dans la base (j'entends par modifier, l'ajout, la suppression et la mise à jour), plutôt que d'utiliser les méthodes correspondantes de l'objet Recordset.

Nous avons vu pourquoi cette technique est fortement recommandable si vous maîtrisez un tant soi peu le SQL, car elle présente le net avantage de savoir exactement ce qu'on demande au SGBD. Néanmoins si vous ne connaissez pas bien le SQL, cela peut représenter plus d'inconvénients que d'avantages.

Attention, passer par des requêtes actions n'est pas sans risque s'il y a plusieurs utilisateurs car on peut obtenir des violations de verrouillage.

V-K. 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 première chose qui doit être optimisée est votre source de données.

Les index manquants, les clés mal construites et surtout une structure anarchique de vos tables engendreront une perte de temps qu'aucune optimisation ne sache compenser. Les axes de l'optimisation sont de deux sortes.

V-K-1. L'optimisation dans l'accès aux données

Celle-ci est propre à la programmation des bases de données.

Utiliser des requêtes compilées

Si vous devez exécuter plusieurs fois une requête. Celle-ci est soit une requête stockée dans la base, soit obtenue en utilisant la propriété "Prepared" de l'objet Command

Utiliser les procédures stockées

Si votre SGBD les supporte, utilisez toujours les procédures stockées plutôt qu'un code ADO équivalent. En effet, les procédures stockées permettent un accès rapide et sécurisé au données, bien au-delà de ce que pourra faire votre code.

Etre restrictif dans les enregistrements / champs renvoyés

En effet, on a trop souvent tendance pour simplifier l'écriture de la requête à rapatrier tous les enregistrements et toutes les colonnes. La plupart du temps, seul certains champs sont utilisés, donc méfiez-vous de l'utilisation du caractère "*". De même, filtrez à l'aide de la clause WHERE quand vous le pouvez.

V-K-2. Optimisation dans l'utilisation d'ADO

Celle-ci est orientée ADO. Attention toutefois, ces effets peuvent être plus ou moins visibles selon le SGBD attaqué.

Utiliser des objets explicites

Méfiez-vous des connexions et commandes implicites, car on a facilement tendance à les oublier ce qui utilise des ressources pour rien.

Fermer les connexions

Ne laissez pas vos connexions ouvertes quand vous n'en avez pas besoin. Cela occupe inutilement le serveur et certains SGBD fermeront une connexion inutilisée un certain temps sans vous envoyer de message d'erreur.

Utiliser une ou deux connexions / commandes

Pensez à réutiliser vos objets au maximum. En général, un objet connection et un objet Command suffisent.

Spécifier adExecuteNoRecords

Précisez toujours ce paramètre lorsqu'une requête ne renvoie pas d'enregistrements ou lorsque vous ne souhaitez pas les récupérer.

Spécifier la propriété CommandType

Si vous ne le précisez pas, ADO devra interpréter CommandText dans un processus assez lourd.


précédentsommairesuivant
Microsoft affirme que la déclaration d'un curseur dynamique (adDynamic) côté serveur renvoie un curseur KeySet qui aurait de meilleures performances sur les gros Recordset qu'un curseur déclaré KeySet. Loin de moi l'idée de mettre en doute une telle affirmation, mais pour ma part je n'ai jamais vu de différence notable.

Copyright © 2003 bidou. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.