Access
 sql >> Base de données >  >> RDS >> Access

Comment Access communique-t-il avec les sources de données ODBC ? Partie 4

Que fait Access lorsqu'un utilisateur modifie les données d'une table liée ODBC ?

Notre série de traçage ODBC se poursuit, et dans ce quatrième article, nous expliquerons comment insérer et mettre à jour un enregistrement de données dans un jeu d'enregistrements, ainsi que le processus de suppression d'un enregistrement. Dans l'article précédent, nous avons appris comment Access gère le remplissage des données à partir de sources ODBC. Nous avons vu que le type de jeu d'enregistrements a un effet important sur la façon dont Access formulera les requêtes à la source de données ODBC. Plus important encore, nous avons constaté qu'avec un jeu d'enregistrements de type feuille de réponse dynamique, Access effectue un travail supplémentaire pour disposer de toutes les informations nécessaires pour pouvoir sélectionner une seule ligne à l'aide d'une clé. Cela s'appliquera dans cet article où nous explorons comment les modifications de données sont gérées. Nous allons commencer par les insertions, qui est l'opération la plus compliquée, puis passer aux mises à jour et enfin aux suppressions.

Insérer un enregistrement dans un jeu d'enregistrements

Le comportement d'insertion d'un jeu d'enregistrements de type feuille de réponse dépend de la manière dont Access perçoit les clés de la table sous-jacente. Il y aura 3 comportements distincts. Les deux premiers traitent de la gestion des clés primaires générées automatiquement par le serveur d'une manière ou d'une autre. Le second est un cas particulier du premier comportement qui s'applique uniquement au backend SQL Server utilisant un IDENTITY colonne. Le dernier traite du cas où les clés sont fournies par l'utilisateur (par exemple, les clés naturelles dans le cadre de la saisie de données). Nous commencerons par le cas plus général des clés générées par le serveur.

Insérer un enregistrement ; une table avec la clé primaire générée par le serveur

Lorsque nous insérons un jeu d'enregistrements (là encore, la manière dont nous procédons, via Access UI ou VBA n'a pas d'importance), Access doit faire des choses pour ajouter la nouvelle ligne au cache local.

La chose importante à noter est qu'Access a des comportements d'insertion différents selon la configuration de la clé. Dans ce cas, les Cities la table n'a pas de IDENTITY attribut mais utilise plutôt une SEQUENCE objet pour générer une nouvelle clé. Voici le SQL tracé formaté :

SQLExecDirect:
INSERT INTO  "Application"."Cities"  (
   "CityName"
  ,"StateProvinceID"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
) VALUES (
   ?
  ,?
  ,?
  ,?)

SQLPrepare:
SELECT
   "CityID"
  ,"CityName"
  ,"StateProvinceID"
  ,"Location"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
  ,"ValidFrom"
  ,"ValidTo"
FROM "Application"."Cities"
WHERE "CityID" IS NULL

SQLExecute: (GOTO BOOKMARK)

SQLExecDirect:
SELECT
  "Application"."Cities"."CityID"
FROM "Application"."Cities"
WHERE "CityName" = ?
  AND "StateProvinceID" = ?
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)
Notez qu'Access ne soumettra que les colonnes qui ont été réellement modifiées par l'utilisateur. Même si la requête elle-même comprenait plus de colonnes, nous n'avons modifié que 4 colonnes, donc Access n'inclura que celles-ci. Cela garantit qu'Access n'interfère pas avec le comportement par défaut défini pour les autres colonnes que l'utilisateur n'a pas modifiées, car Access n'a aucune connaissance spécifique de la manière dont la source de données gérera ces colonnes. Au-delà de cela, l'instruction d'insertion correspond à peu près à ce à quoi nous nous attendions.

La deuxième déclaration, cependant, est un peu étrange. Il sélectionne pour WHERE "CityID" IS NULL . Cela semble impossible, puisque nous savons déjà que le CityID colonne est une clé primaire et par définition ne peut pas être nulle. Cependant, si vous regardez la capture d'écran, nous n'avons jamais modifié le CityID colonne. Depuis le POV d'Access, c'est NULL . Très probablement, Access adopte une approche pessimiste et ne supposera pas que la source de données respectera en fait la norme SQL. Comme nous l'avons vu dans la section expliquant comment Access sélectionne un index à utiliser pour identifier de manière unique une ligne, il se peut qu'il ne s'agisse pas d'une clé primaire, mais simplement d'un UNIQUE index qui peut autoriser NULL . Pour ce cas marginal improbable, il effectue une requête juste pour s'assurer que la source de données n'a pas réellement créé un nouvel enregistrement avec cette valeur. Une fois qu'il a examiné qu'aucune donnée n'a été renvoyée, il tente alors de localiser à nouveau l'enregistrement avec le filtre suivant :

WHERE "CityName" = ?
  AND "StateProvinceID" = ?
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
qui étaient les mêmes 4 colonnes que l'utilisateur a réellement modifiées. Puisqu'il n'y avait qu'une seule ville nommée "Zeke", nous n'avons récupéré qu'un seul enregistrement, et Access peut alors remplir le cache local avec le nouvel enregistrement avec les mêmes données que la source de données. Il intégrera toutes les modifications apportées aux autres colonnes, puisque le SELECT la liste inclut uniquement le CityID key, qu'il utilisera ensuite dans sa déclaration déjà préparée pour ensuite remplir la ligne entière en utilisant le CityID clé.

Insérer un enregistrement ; une table avec clé primaire auto-incrémentée

Cependant, que se passe-t-il si la table provient d'une base de données SQL Server et comporte une colonne d'auto-incrémentation telle que IDENTITY ? attribut? L'accès se comporte différemment. Créons donc une copie des Cities table mais modifiez-la pour que le CityID la colonne est maintenant une IDENTITY colonne.

Voyons comment Access gère cela :

SQLExecDirect:
INSERT INTO "Application"."Cities" (
   "CityName"
  ,"StateProvinceID"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
  ,"ValidFrom"
  ,"ValidTo"
) VALUES (
   ?
  ,?
  ,?
  ,?
  ,?
  ,?)

SQLExecDirect:
SELECT @@IDENTITY

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (GOTO BOOKMARK)
Il y a beaucoup moins de bavardage; nous faisons simplement un SELECT @@IDENTITY pour trouver l'identité nouvellement insérée. Malheureusement, ce n'est pas un comportement général. Par exemple, MySQL prend en charge la possibilité de faire un SELECT @@IDENTITY , cependant, Access ne fournira pas ce comportement. Le pilote ODBC PostgreSQL a un mode pour émuler SQL Server afin d'inciter Access à envoyer le @@IDENTITY à PostgreSQL afin qu'il puisse être mappé sur l'équivalent serial type de données.

Insérer un enregistrement avec une valeur explicite pour la clé primaire

Faisons une 3ème expérience en utilisant une table avec un int normal colonne, sans IDENTITY attribut. Bien qu'il s'agisse toujours d'une clé primaire sur la table, nous voudrons voir comment elle se comporte lorsque nous insérons explicitement la clé nous-mêmes.

SQLExecDirect:
INSERT INTO  "Application"."Cities" (
   "CityID"
  ,"CityName"
  ,"StateProvinceID"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
  ,"ValidFrom"
  ,"ValidTo"
) VALUES (
   ?
  ,?
  ,?
  ,?
  ,?
  ,?
  ,?
)

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)
Cette fois-ci, il n'y a pas de gymnastique supplémentaire; puisque nous avons déjà fourni la valeur de la clé primaire, Access sait qu'il n'a pas à essayer de retrouver la ligne ; il exécute simplement l'instruction préparée pour resynchroniser la ligne insérée. Revenant à la conception originale où les Cities table a utilisé une SEQUENCE objet pour générer une nouvelle clé, nous pouvons ajouter une fonction VBA pour récupérer le nouveau numéro en utilisant NEXT VALUE FOR et ainsi remplir la clé de manière proactive pour nous obtenir ce comportement. Cela se rapproche plus du fonctionnement du moteur de base de données Access ; dès que nous salissons un enregistrement, il récupère une nouvelle clé à partir du AutoNumber type de données, plutôt que d'attendre que l'enregistrement ait été réellement inséré. Ainsi, si votre base de données utilise SEQUENCE ou d'autres façons de créer des clés, il peut être avantageux de fournir un mécanisme de récupération proactive de la clé pour aider à réduire les conjectures que nous avons vu Access faire avec le premier exemple.

Mise à jour d'un enregistrement dans un jeu d'enregistrements

Contrairement aux encarts de la section précédente, les mises à jour sont relativement plus faciles car nous avons déjà la clé présente. Ainsi, Access se comporte généralement plus simplement en matière de mise à jour. Il y a deux comportements majeurs que nous devons prendre en compte lors de la mise à jour d'un enregistrement qui dépend de la présence d'une colonne rowversion.

Mise à jour d'un enregistrement sans colonne rowversion

Supposons que nous modifions une seule colonne. C'est ce que nous voyons dans ODBC.

SQLExecute: (GOTO BOOKMARK)

SQLExecDirect:
UPDATE "Application"."Cities"
SET "CityName"=?
WHERE "CityID" = ?
  AND "CityName" = ?
  AND "StateProvinceID" = ?
  AND "Location" IS NULL
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
  AND "ValidFrom" = ?
  AND "ValidTo" = ?
Hmm, quel est le problème avec toutes ces colonnes supplémentaires que nous n'avons pas modifiées ? Eh bien, encore une fois, Access doit adopter une vision pessimiste. Il doit supposer que quelqu'un aurait pu modifier les données pendant que l'utilisateur tâtonnait lentement dans les modifications. Mais comment Access saurait-il que quelqu'un d'autre a modifié les données sur le serveur ? Eh bien, logiquement, si toutes les colonnes sont exactement les mêmes, alors il n'aurait dû mettre à jour qu'une seule ligne, n'est-ce pas ? C'est ce que recherche Access lorsqu'il compare toutes les colonnes ; pour s'assurer que la mise à jour n'affectera qu'une seule ligne. S'il constate qu'il a mis à jour plus d'une ligne ou zéro ligne, il annule la mise à jour et renvoie soit une erreur, soit #Deleted à l'utilisateur.

Mais… c'est un peu inefficace, n'est-ce pas ? De plus, cela pourrait poser problème s'il existe une logique côté serveur susceptible de modifier les valeurs saisies par l'utilisateur. Pour illustrer, supposons que nous ajoutions un déclencheur idiot qui change le nom de la ville (nous ne le recommandons pas, bien sûr) :

CREATE TRIGGER SillyTrigger
ON Application.Cities AFTER UPDATE AS
BEGIN
  UPDATE Application.Cities
  SET CityName = 'zzzzz'
  WHERE EXISTS (
    SELECT NULL
    FROM inserted AS i
    WHERE Cities.CityID = i.CityID
  );
END;
Donc, si nous essayons ensuite de mettre à jour une ligne en changeant le nom de la ville, cela semblera avoir réussi.

Mais si nous essayons ensuite de le modifier à nouveau, nous obtenons un message d'erreur avec un message actualisé :

Ceci est la sortie de sqlout.txt :

SQLExecDirect:
UPDATE "Application"."Cities"
SET "CityName"=?
WHERE "CityID" = ?
  AND "CityName" = ?
  AND "StateProvinceID" = ?
  AND "Location" IS NULL
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
  AND "ValidFrom" = ?
  AND "ValidTo" = ?

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)

SQLExecute: (MULTI-ROW FETCH)
Il est important de noter que le 2ème GOTO BOOKMARK et le suivant MULTI-ROW FETCH es ne s'est pas produit jusqu'à ce que nous recevions le message d'erreur et que nous l'ayons rejeté. La raison en est que lorsque nous salissons un enregistrement, Access effectue un GOTO BOOKMARK , réalisez que les données renvoyées ne correspondent plus à ce qu'elles ont sur le cache, ce qui nous fait recevoir le message "Les données ont été modifiées". Cela nous évite de perdre du temps à éditer un enregistrement voué à l'échec car déjà obsolète. Notez qu'Access finirait également par découvrir le changement si nous lui laissions suffisamment de temps pour actualiser les données. Dans ce cas, il n'y aurait pas de message d'erreur; la fiche technique serait simplement mise à jour pour afficher les données correctes.

Dans ces cas, cependant, Access disposait de la bonne clé, de sorte qu'il n'avait aucun problème à découvrir les nouvelles données. Mais si c'est la clé qui est fragile ? Si le déclencheur avait changé la clé primaire ou si la source de données ODBC ne représentait pas la valeur exactement comme Access le pensait, cela obligerait Access à peindre l'enregistrement comme #Deleted car il ne peut pas savoir s'il a été modifié par le serveur ou quelqu'un d'autre ou s'il a été légitimement supprimé par quelqu'un d'autre.

Mise à jour d'un enregistrement avec la colonne rowversion

Dans tous les cas, obtenir un message d'erreur ou #Deleted peut être assez ennuyeux. Mais il existe un moyen d'empêcher Access de comparer toutes les colonnes. Supprimons le déclencheur et ajoutons une nouvelle colonne :

ALTER TABLE Application.Cities
ADD RV rowversion NOT NULL;
Nous ajoutons une rowversion qui a la propriété d'être exposé à ODBC comme ayant SQLSpecialColumns(SQL_ROWVER) , c'est ce dont Access a besoin pour savoir qu'il peut être utilisé comme moyen de versionner la ligne. Voyons comment les mises à jour fonctionnent avec ce changement.

SQLExecDirect: UPDATE "Application"."Cities" 
SET "CityName"=?  
WHERE "CityID" = ? 
  AND "RV" = ?

SQLExecute: (GOTO BOOKMARK)
Contrairement à l'exemple précédent où Access comparait la valeur de chaque colonne, que l'utilisateur l'ait modifiée ou non, nous ne mettons à jour l'enregistrement qu'à l'aide du RV comme critères de filtrage. Le raisonnement est que si le RV a toujours la même valeur que celle transmise par Access, alors Access peut être sûr que cette ligne n'a été modifiée par personne d'autre, car si c'était le cas, alors le RV la valeur aurait changé.

Cela signifie également que si un déclencheur modifie les données ou si SQL Server et Access ne représentent pas une valeur exactement de la même manière (par exemple, des nombres flottants), Access ne rechignera pas lorsqu'il resélectionnera la ligne mise à jour et qu'il reviendra avec différents valeurs dans d'autres colonnes que les utilisateurs n'ont pas modifiées.

REMARQUE :Tous les produits SGBD n'utiliseront pas les mêmes termes. Par exemple, l'timestamp de MySQL peut être utilisé comme version de ligne pour les besoins d'ODBC. Vous devrez consulter la documentation du produit pour voir s'il prend en charge la fonctionnalité rowversion afin de pouvoir tirer parti de ce comportement avec Access.

Vues et version de ligne

Les vues sont également affectées par la présence ou l'absence d'une version de ligne. Supposons que nous créons une vue dans SQL Server avec la définition :

CREATE VIEW dbo.vwCities AS
SELECT
	CityID,
	CityName
FROM Application.Cities;
La mise à jour d'un enregistrement sur la vue reviendrait à la comparaison colonne par colonne comme si la colonne rowversion n'existait pas sur la table :

SQLExecDirect: UPDATE "dbo"."vwCities" SET "CityName"=?  WHERE "CityID" = ? AND "CityName" = ?
Par conséquent, si vous avez besoin du comportement de mise à jour basé sur rowversion, vous devez veiller à ce que les colonnes rowversion soient incluses dans les vues. Dans le cas d'une vue qui contient plusieurs tables dans des jointures, il est préférable d'inclure au moins les colonnes rowversion de la ou des table(s) où vous avez l'intention de mettre à jour. Étant donné qu'en règle générale, une seule table peut être mise à jour, l'inclusion d'une seule version de ligne peut suffire en règle générale.

Supprimer un enregistrement dans un jeu d'enregistrements

La suppression d'un enregistrement se comporte de la même manière que les mises à jour et utilisera également rowversion si disponible. Sur une table sans rowversion, on obtient :

SQLExecDirect: 
DELETE FROM "Application"."Cities" 
WHERE "CityID" = ? 
  AND "CityName" = ? 
  AND "StateProvinceID" = ? 
  AND "Location" IS NULL 
  AND "LatestRecordedPopulation" = ? 
  AND "LastEditedBy" = ? 
  AND "ValidFrom" = ? 
  AND "ValidTo" = ?
Sur une table avec une rowversion, on obtient :

SQLExecDirect: 
DELETE FROM "Application"."Cities" 
WHERE "CityID" = ? 
  AND "RV" = ?
Encore une fois, Access doit être pessimiste quant à la suppression car il s'agit de la mise à jour; il ne voudrait pas supprimer une ligne qui a été modifiée par quelqu'un d'autre. Ainsi, il utilise le même comportement que nous avons vu avec la mise à jour pour éviter que plusieurs utilisateurs ne modifient les mêmes enregistrements.

Conclusion

Nous avons appris comment Access gère les modifications de données et maintient son cache local en synchronisation avec la source de données ODBC. Nous avons vu à quel point Access était pessimiste, motivé par la nécessité de prendre en charge autant de sources de données ODBC que possible sans s'appuyer sur des hypothèses ou des attentes spécifiques selon lesquelles ces sources de données ODBC prendront en charge une certaine fonctionnalité. Pour cette raison, nous avons vu qu'Access se comportera différemment selon la façon dont la clé est définie pour une table liée ODBC donnée. Si nous pouvions insérer explicitement une nouvelle clé, cela nécessitait un travail minimal de la part d'Access pour resynchroniser le cache local pour l'enregistrement nouvellement inséré. Cependant, si nous autorisons le serveur à remplir la clé, Access devra effectuer un travail supplémentaire en arrière-plan pour se resynchroniser.

Nous avons également vu que le fait d'avoir une colonne sur la table qui peut être utilisée comme version de ligne peut aider à réduire le bavardage entre Access et la source de données ODBC lors d'une mise à jour. Vous devrez consulter la documentation du pilote ODBC pour déterminer s'il prend en charge la version de ligne au niveau de la couche ODBC et, le cas échéant, inclure cette colonne dans les tables ou les vues avant de créer un lien vers Access pour bénéficier des avantages des mises à jour basées sur la version de ligne.

Nous savons maintenant que pour toute mise à jour ou suppression, Access essaiera toujours de vérifier que la ligne n'a pas été modifiée depuis sa dernière récupération par Access, afin d'empêcher les utilisateurs d'apporter des modifications qui pourraient être inattendues. Cependant, nous devons tenir compte des effets résultant de modifications apportées à d'autres endroits (par exemple, déclencheur côté serveur, exécution d'une requête différente dans une autre connexion) qui peuvent amener Access à conclure que la ligne a été modifiée et donc à interdire la modification. Ces informations nous aideront à analyser et à éviter de créer une séquence de modifications de données pouvant contredire les attentes d'Access lors de la resynchronisation du cache local.

Dans le prochain article, nous examinerons les effets de l'application de filtres sur un jeu d'enregistrements.

Obtenez l'aide de nos experts en accès dès aujourd'hui. Appelez notre équipe au 773-809-5456 ou envoyez-nous un e-mail à [email protected].