Depuis Access 2010, Access prend en charge le type de données Pièces jointes qui, à première vue, semble être une fonctionnalité pratique pour stocker de petites images ou des fichiers. Cependant, une recherche rapide sur Google montrera généralement qu'il vaut mieux les éviter. Tout cela se résume au fait qu'un type de données Pièces jointes est en fait un champ à valeurs multiples (MVF), et ceux-ci posent plusieurs problèmes. D'une part, vous seriez incapable d'utiliser des requêtes pour insérer ou mettre à jour plusieurs enregistrements en une seule fois. En effet, toutes les tables qui contiennent ce type de données vous obligent à faire beaucoup de code et pour cette seule raison, nous évitons d'utiliser ces types de données normalement.
Cependant, il y a un problème. Nous adorons utiliser la galerie d'images et les thèmes, qui dépendent tous deux d'une table système, MSysResources
qui utilise malheureusement les types de données de pièce jointe. Cela a créé un problème pour la gestion des ressources dans notre bibliothèque standard car nous voulons utiliser les MSysResources
mais nous ne pouvons pas facilement les mettre à jour ou les insérer en masse.
Le type de données de pièce jointe (ainsi que les MVF) vous oblige à utiliser la programmation "ligne par ligne agonisante" lorsqu'il s'agit d'un champ MVF, c'est un double avec le champ Pièces jointes car vous devrez utiliser le LoadFromFile
ou SaveToFile
méthodes. Microsoft a un article avec des exemples sur ces méthodes. Ainsi, vous devez interagir avec le système de fichiers lors de l'ajout de nouveaux enregistrements. Pas toujours souhaitable dans toutes les situations. Maintenant, si nous copions d'une table à une autre, nous pouvons éviter de rebondir sur le système de fichiers en faisant quelque chose comme :
Dim SourceParentRs As DAO.Recordset2 Dim SourceChildRs As DAO.Recordset2 Dim TargetParentRs As DAO.Recordset2 Dim TargetChildRs As DAO.Recordset2 Dim SourceField As DAO.Field2 Set SourceParentRs = db.OpenRecordset("TableWithAttachmentField", dbOpenDynaset) Set TargetParentRs = db.OpenRecordset("AnotherTableWithAttachmentField", dbOpenDynaset, dbAppendOnly) Do Until SourceParentRs.EOF TargetParentRs.AddNew For Each SourceField In SourceParentRs.Fields If SourceField.Type <> dbAttachment Then TargetParentRs.Fields(SourceField.Name).Value = SourceField.Value End If Next TargetParentRs.Update 'Must save record first before can edit MVF fields TargetParentRs.Bookmark = TargetParentRs.LastModified Set SourceChildRs = SourceParentRs.Fields("Data").Value Set TargetChildRs = TargetParentRs.Fields("Data").Value Do Until SourcechildRs.EOF TargetChildRs.AddNew Const ChunkSize As Long = 32768 Dim TotalSize As Long Dim Offset As Long TotalSize = SourceChildRs.Fields("FileData").FieldSize Offset = TotalSize Mod ChunkSize TargetChildRs.Fields("FileData").AppendChunk(SourceChildRs.GetChunk(0, Offset) Do Until Offset > TotalSize TargetChildRs.Fields("FileData").AppendChunk(SourceChildRs.GetChunk(Offset, ChunkSize) Offset = Offset + ChunkSize Loop TargetChildRs.Update SourceChildRs.MoveNext Loop TargetParentRs.Update SourceParentRs.MoveNext Loop
Sacré boucle, Batman ! C'est beaucoup de code, tout simplement pour copier des pièces jointes d'une table à une autre. Même si nous ne rebondissons pas sur le système de fichiers, il est également très lent. D'après notre expérience, une table avec 1 000 enregistrements contenant une seule pièce jointe peut prendre minutes juste pour traiter. Maintenant, c'est assez démesuré quand on considère la taille. La table avec les pièces jointes n'est pas si grande. En fait, faisons une expérience. Voyons ce qui se passe si je copie et colle via la fiche technique :
Ainsi, le copier-coller est pratiquement instantané. Évidemment, le code utilisé par collage n'est pas le même code que nous utiliserions dans VBA. Cependant, nous croyons fermement que si nous pouvons le faire de manière interactive, nous pouvons également le faire en VBA. Pouvons-nous reproduire la vitesse du collage interactif dans VBA ? La réponse s'avère être oui, nous le pouvons !
Accélérez avec …. XML ?
Étonnamment, la méthode qui offre le moyen le plus rapide de copier des données, y compris des pièces jointes, consiste à utiliser des fichiers XML. J'admets que je n'utilise pas les fichiers XML, sauf pour contourner les limitations. En moyenne, les fichiers XML sont relativement lents par rapport aux autres formats de fichiers, mais dans ce cas, XML présente un énorme avantage ; il n'a aucun problème à décrire les MVF. Créons un fichier XML et étudions les capacités que nous obtenons avec l'importation/exportation d'un fichier XML.
Après la boîte de dialogue habituelle de l'assistant d'exportation pour définir le chemin d'enregistrement du fichier XML, nous aurons une boîte de dialogue comme celle-ci :
Si nous cliquons ensuite sur le bouton "Plus d'options…", nous obtenons cette boîte de dialogue à la place :
À partir de cette boîte de dialogue, nous voyons quelques indices supplémentaires sur ce qui est possible ; à savoir :
- Nous avons la possibilité d'exporter l'intégralité du tableau ou seulement un sous-ensemble du tableau en appliquant un filtre
- Nous pouvons transformer la sortie XML.
- Nous pouvons décrire le schéma en plus du contenu du tableau.
Je trouve qu'il est préférable d'intégrer le schéma ; la valeur par défaut est de l'exporter mais dans un fichier séparé. Cependant, cela peut être source d'erreurs et ils peuvent oublier d'inclure le fichier XSD avec le fichier XML. Cela peut être modifié via l'onglet schéma affiché :
Finissons de l'exporter et examinons rapidement les données du fichier XML résultant.
Notez que les pièces jointes sont décrites dans les Data
le contenu de la sous-arborescence et du fichier est encodé en base 64. Essayons d'importer le fichier XML. Après avoir parcouru l'assistant d'importation, nous obtiendrons cette boîte de dialogue :
Prenez note des fonctionnalités suivantes :
- Comme pour l'exportation, nous avons la possibilité de transformer le XML.
- Nous pouvons contrôler s'il faut importer la structure, les données ou les deux
Si nous finissons ensuite d'importer le fichier XML, nous constatons que c'est aussi rapide que l'opération de copier-coller que nous avons faite.
Nous savons maintenant qu'il existe un meilleur moyen de copier plusieurs enregistrements avec des pièces jointes. Mais dans cette situation, nous voulons le faire par programmation plutôt qu'interactivement. Pouvons-nous faire la même chose que nous venons de faire ? A nouveau la réponse est oui. Il y a plusieurs façons de faire la même chose mais je pense que la méthode la plus simple est d'utiliser les 3 nouvelles méthodes qui ont été ajoutées à l'Application
objet depuis Access 2010 :
ExportXML
méthodeTransformXML
méthodeImportXML
méthode
Notez que le ExportXML
prend en charge l'exportation à partir de divers objets. Cependant, comme l'objectif ici est de pouvoir copier ou mettre à jour en masse les enregistrements d'une table avec des champs de pièce jointe, le meilleur type d'objet que nous pouvons utiliser est une requête enregistrée. Avec une requête enregistrée, nous pouvons contrôler quelles lignes doivent être insérées ou mises à jour et nous pouvons également façonner la sortie. Si vous regardez la conception du schéma de MSysResources
tableau ci-dessous :
Il y a un problème potentiel. Chaque fois que nous utilisons des thèmes ou des images, nous référençons l'élément par son nom, et non par son identifiant. Cependant, le Name
colonne n'est pas unique et n'est pas la clé primaire de la table. Par conséquent, lorsque nous ajoutons ou mettons à jour des enregistrements, nous voulons faire correspondre le Name
colonne, pas l'Id
colonne. Cela signifie que lorsque nous exportons, nous ne devrions probablement pas inclure le Id
colonne et nous devrions exporter uniquement la liste unique du Name
pour s'assurer que les ressources ne passent pas soudainement de "Open.png" à "Close.png" ou quelque chose de stupide.
Nous allons ensuite créer une requête pour servir de source pour les enregistrements que nous voulons importer dans le MSysResources
table. Commençons par ce SQL juste pour démontrer le filtrage vers un sous-ensemble d'enregistrements :
SELECT e.Data, e.Extension, e.Name, e.Type FROM Example AS e WHERE e.Name In ("blue","red","green");
Nous l'enregistrerons ensuite sous qryResourcesExport
. On peut alors écrire du code VBA pour exporter du XML :
Application.ExportXML _ ObjectType:=acExportQuery, _ DataSource:="qryResourcesExport", _ DataTarget:="C:\Path\to\Resources.xml", _ OtherFlags:=acEmbedSchema
Cela émule l'exportation que nous avons initialement effectuée de manière interactive.
Cependant, si nous importons ensuite le XML résultant, nous n'avons que la possibilité d'ajouter des données à une table existante. Nous ne pouvons pas contrôler dans quelle table il sera ajouté ; il trouvera une table ou une table de requête du même nom (par exemple, qryResourcesExport
et ajouter des enregistrements dans cette requête. Si la requête peut être mise à jour, alors il n'y a pas de problème et elle sera insérée dans l'Example
sur lequel la requête est basée. Mais que se passe-t-il si la requête source que nous utilisons ne peut pas être mise à jour ou n'existe pas ? Dans les deux cas, nous ne serions pas en mesure d'importer le fichier XML tel quel. L'importation peut échouer ou créer une nouvelle table nommée qryResourcesExport
qui ne nous aide pas. Et qu'en est-il du cas de la copie de données depuis Example
vers MSysResources
? Nous ne voulons pas ajouter de données à l'Example
tableau.
C'est là que le TransformXML
méthode vient à la rescousse. Une discussion complète sur la façon d'écrire une transformation XML dépasse le cadre, mais vous devriez pouvoir trouver de nombreuses ressources sur la façon d'écrire une feuille de style XSLT pour décrire la transformation. Il existe plusieurs outils en ligne que vous pouvez également utiliser pour valider votre XSLT. En voici un. Pour le cas simple où nous voulons simplement contrôler dans quelle table le fichier XML doit ajouter les enregistrements, vous pouvez commencer avec ce fichier XSLT. Vous pouvez ensuite exécuter le code VBA suivant :
Application.TransformXML _ DataSource:="C:\Path\to\Resources.xml", _ TransformSource:="C:\Path\to\ResourcesTransform.xslt", _ OutputTarget:="C:\Path\to\Resources.xml", _ WellFormedXMLOutput:=True, _ ScriptOption:=acEnableScript
Nous pouvons remplacer le fichier XML d'origine par le fichier XML transformé, qui va maintenant s'insérer dans le MSysResources
table plutôt que dans (peut-être une requête/table inexistante) qryResourcesExport
.
Nous devons ensuite gérer les mises à jour. Parce que nous ajoutons en fait de nouveaux enregistrements, et le MSysResources
table n'a aucune contrainte sur les noms en double, nous devons nous assurer que tous les enregistrements existants portant le même nom sont d'abord supprimés. Cela peut être accompli en écrivant une requête équivalente comme ceci :
DELETE FROM MSysResources AS r WHERE r.Name In ("blue","red","green");
puis l'exécuter avant d'exécuter le code VBA :
Application.ImportXML DataSource:="C:\Path\to\Resources.xml", ImportOptions:=acAppendData
Parce que le fichier XML a été transformé, le ImportXML
La méthode va maintenant insérer les données dans le MSysResources
table plutôt que la requête d'origine que nous avons utilisée avec le ExportXML
méthode. Nous spécifions qu'il doit ajouter des données dans une table existante. Cependant, si la table n'existe pas, elle sera créée.
Et avec cela, nous avons réalisé une mise à jour/insertion en masse de la table avec un champ de pièce jointe qui est beaucoup plus rapide par rapport au code VBA original du jeu d'enregistrements et du jeu d'enregistrements enfant. J'espère que cela pourra aider! De plus, si vous avez besoin d'aide pour développer des applications Access, n'hésitez pas à nous contacter !