Access
 sql >> Database >  >> RDS >> Access

Inserimento in blocco o aggiornamento per tabelle con campi allegato

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 metodo
  • TransformXML metodo
  • ImportXML 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!