Da Access 2010, Access supporta il tipo di dati Allegati che a prima vista sembra una comoda funzionalità per l'archiviazione di piccole immagini o file. Tuttavia, una rapida ricerca su Google di solito mostra che è meglio evitarli. Tutto ciò si riduce al fatto che un tipo di dati Allegati è in realtà un campo multivalore (MVF) e questi presentano diversi problemi. Per uno, non saresti in grado di utilizzare le query per inserire o aggiornare più record in una volta sola. In effetti, tutte le tabelle che contengono tale tipo di dati ti obbligano a eseguire molto codice e solo per questo motivo evitiamo di utilizzare normalmente tali tipi di dati.
Tuttavia, c'è un problema. Ci piace usare la galleria di immagini e i temi, che dipendono entrambi da una tabella di sistema, MSysResources
che purtroppo utilizza i tipi di dati degli allegati. Questo ha creato un problema per la gestione delle risorse nella nostra libreria standard perché vogliamo usare MSysResources
ma non possiamo aggiornarli o inserirli facilmente in blocco.
Il tipo di dati dell'allegato (così come gli MVF) ti obbliga a utilizzare la programmazione "riga per riga agonizzante" quando hai a che fare con un campo MVF, è un doppio con il campo Allegati perché dovresti usare il LoadFromFile
o SaveToFile
metodi. Microsoft ha un articolo con esempi su questi metodi. Pertanto, è necessario interagire con il filesystem quando si aggiungono nuovi record. Non sempre desiderabile in tutte le situazioni. Ora, se stiamo copiando da una tabella a un'altra, possiamo evitare di rimbalzare sul filesystem facendo qualcosa come:
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
Santo giro, Batman! È un sacco di codice, tutto solo per copiare gli allegati da una tabella all'altra. Anche se non rimbalziamo sul filesystem, è anche molto lento. In base alla nostra esperienza, una tabella con 1000 record contenenti un singolo allegato può richiedere minuti solo per elaborare. Ora, questo è piuttosto fuori misura se consideri le dimensioni. Il tavolo con gli allegati non è così grande. In effetti, facciamo un esperimento. Vediamo cosa succede se copio e incollo tramite foglio dati:
Quindi copiare e incollare è praticamente istantaneo. Ovviamente il codice utilizzato per incollare non è lo stesso codice che useremmo in VBA. Tuttavia, crediamo fermamente che se possiamo farlo in modo interattivo, possiamo farlo anche in VBA. Possiamo replicare la velocità dell'incollaggio interattivo in VBA? La risposta risulta essere sì, possiamo!
Accelera con …. XML?
Sorprendentemente il metodo che fornisce il modo più veloce per copiare i dati, inclusi gli allegati, è tramite file XML. Ammetto che non raggiungo i file XML se non come soluzione alternativa per le limitazioni. In media, i file XML sono relativamente lenti rispetto ad altri formati di file, ma in questo caso XML ha un enorme vantaggio; non ha problemi a descrivere gli MVF. Creiamo un file XML ed esaminiamo le capacità che otteniamo importando/esportando un file XML.
Dopo la consueta finestra di dialogo della procedura guidata di esportazione per impostare il percorso per salvare il file XML, avremo una finestra di dialogo come questa:
Se quindi facciamo clic sul pulsante "Altre opzioni...", otteniamo invece questa finestra di dialogo:
Da questa finestra di dialogo, vediamo alcuni indizi in più su ciò che è possibile; vale a dire:
- Abbiamo la possibilità di esportare l'intera tabella o solo un sottoinsieme della tabella applicando un filtro
- Possiamo trasformare l'output XML.
- Possiamo descrivere lo schema oltre al contenuto della tabella.
Trovo che sia meglio incorporare lo schema; l'impostazione predefinita è esportarlo ma come file separato. Tuttavia, ciò può essere soggetto a errori e possono dimenticare di includere il file XSD con il file XML. Questo può essere modificato tramite la scheda dello schema mostrata:
Concludiamo l'esportazione e diamo una rapida occhiata ai dati del file XML risultante.
Nota che gli allegati sono descritti all'interno dei Data
il contenuto della sottostruttura e del file è codificato in base 64. Proviamo a importare il file XML. Dopo aver eseguito la procedura guidata di importazione, otterremo questa finestra di dialogo:
Prendi nota delle seguenti caratteristiche:
- Come per l'esportazione, abbiamo la possibilità di trasformare l'XML.
- Possiamo controllare se importare la struttura, i dati o entrambi
Se poi finiamo di importare il file XML, scopriamo che è veloce quanto l'operazione di copia e incolla che abbiamo eseguito.
Ora sappiamo che esiste un percorso migliore per copiare diversi record con allegati. Ma in questa situazione, vogliamo farlo in modo programmatico, piuttosto che interattivo. Possiamo fare la stessa cosa che abbiamo appena fatto? Ancora una volta, la risposta è sì. Esistono diversi modi per fare la stessa cosa, ma penso che il metodo più semplice sia utilizzare i 3 nuovi metodi che sono stati aggiunti all'Application
oggetto da Access 2010:
ExportXML
metodoTransformXML
metodoImportXML
metodo
Nota che ExportXML
il metodo supporta l'esportazione da vari oggetti. Tuttavia, poiché l'obiettivo qui è poter copiare o aggiornare in blocco i record di una tabella con campi allegati, il miglior tipo di oggetto da utilizzare è una query salvata. Con una query salvata, possiamo controllare quali righe devono essere inserite o aggiornate e possiamo anche modellare l'output. Se osservi la progettazione dello schema di MSysResources
tabella seguente:
C'è un potenziale problema. Ogni volta che utilizziamo temi o immagini, facciamo riferimento all'articolo per nome, non per ID. Tuttavia, il Name
colonna non è univoca e non è la chiave primaria della tabella. Pertanto, quando aggiungiamo o aggiorniamo record, vogliamo che corrispondano al Name
colonna, non l'Id
colonna. Ciò significa che quando esportiamo, probabilmente non dovremmo includere l'Id
colonna e dovremmo esportare solo l'elenco univoco del Name
per garantire che le risorse non passino improvvisamente da "Open.png" a "Close.png" o qualcosa di stupido.
Creeremo quindi una query che funga da origine per i record che vogliamo importare in MSysResources
tavolo. Iniziamo con questo SQL solo per dimostrare il filtraggio fino a un sottoinsieme di record:
SELECT e.Data, e.Extension, e.Name, e.Type FROM Example AS e WHERE e.Name In ("blue","red","green");
Lo salveremo quindi come qryResourcesExport
. Possiamo quindi scrivere codice VBA per esportare XML:
Application.ExportXML _ ObjectType:=acExportQuery, _ DataSource:="qryResourcesExport", _ DataTarget:="C:\Path\to\Resources.xml", _ OtherFlags:=acEmbedSchema
Questo emula l'esportazione che originariamente eseguivamo in modo interattivo.
Tuttavia, se poi importiamo l'XML risultante, abbiamo solo la possibilità di aggiungere dati a una tabella esistente. Non possiamo controllare in quale tabella verrà aggiunto; troverà una tabella o una tabella di query con lo stesso nome (ad es. qryResourcesExport
e aggiungi i record a quella query. Se la query è aggiornabile, non ci sono problemi e verrà inserita nell'Example
su cui si basa la query. Ma cosa succede se la query di origine che utilizziamo non è aggiornabile o potrebbe non esistere? In entrambi i casi, non saremo in grado di importare il file XML così com'è. Potrebbe non riuscire a importare o finire per creare una nuova tabella denominata qryResourcesExport
che non ci aiuta. E che dire del caso di copiare i dati da Example
a MSysResources
? Non vogliamo aggiungere dati all'Example
tabella.
Ecco dove si trova il TransformXML
il metodo viene in soccorso. Una discussione completa su come scrivere una trasformazione XML va oltre lo scopo, ma dovresti essere in grado di trovare ampie risorse su come scrivere un foglio di stile XSLT per descrivere la trasformazione. Esistono anche diversi strumenti online che puoi utilizzare per convalidare il tuo XSLT. Eccone uno. Per il semplice caso in cui vogliamo solo controllare in quale tabella il file XML deve aggiungere i record, puoi iniziare con questo file XSLT. È quindi possibile eseguire il seguente codice VBA:
Application.TransformXML _ DataSource:="C:\Path\to\Resources.xml", _ TransformSource:="C:\Path\to\ResourcesTransform.xslt", _ OutputTarget:="C:\Path\to\Resources.xml", _ WellFormedXMLOutput:=True, _ ScriptOption:=acEnableScript
Possiamo sostituire il file XML originale con il file XML trasformato, che ora verrà inserito in MSysResources
tabella anziché in (possibilmente query/tabella inesistente) qryResourcesExport
.
Quindi dobbiamo gestire gli aggiornamenti. Perché in realtà stiamo aggiungendo nuovi record e MSysResources
table non ha alcun vincolo sui nomi duplicati, dobbiamo assicurarci che tutti i record esistenti con gli stessi nomi vengano prima eliminati. Questo può essere ottenuto scrivendo una query equivalente in questo modo:
DELETE FROM MSysResources AS r WHERE r.Name In ("blue","red","green");
quindi eseguirlo prima di eseguire il codice VBA:
Application.ImportXML DataSource:="C:\Path\to\Resources.xml", ImportOptions:=acAppendData
Poiché il file XML è stato trasformato, ImportXML
il metodo ora inserirà i dati in MSysResources
tabella anziché la query originale che abbiamo usato con ExportXML
metodo. Specifichiamo che dovrebbe aggiungere dati in una tabella esistente. Tuttavia, se la tabella non esiste, verrà creata.
E con ciò, abbiamo ottenuto un aggiornamento/inserimento di massa della tabella con un campo allegato che è molto più veloce rispetto al codice VBA recordset-and-child-recordset originale. Spero che sia d'aiuto! Inoltre, se hai bisogno di aiuto per lo sviluppo di applicazioni Access, non esitare a contattarci!