Sedan Access 2010 har Access stödt bilagor datatyp som på ytan verkar vara en bekväm funktion för att lagra små bilder eller filer. Men en snabb sökning på google visar vanligtvis att de bäst undviks. Allt detta kokar ner till det faktum att en datatyp för bilagor faktiskt är ett multi-Valued Field (MVF), och dessa kommer med flera problem. För det första skulle du inte kunna använda frågor för att infoga eller uppdatera flera poster på en gång. Faktum är att alla tabeller som innehåller sådana datatyper tvingar dig att göra mycket kod och bara av den anledningen undviker vi att använda sådana datatyper normalt.
Det finns dock ett problem. Vi älskar att använda bildgalleriet och teman, som båda beror på en systemtabell, MSysResources
som tyvärr använder bifogade datatyper. Detta har skapat ett problem för att hantera resurser i vårt standardbibliotek eftersom vi vill använda MSysResources
men vi kan inte enkelt uppdatera eller infoga dem samtidigt.
Bifogade datatyp (liksom MVFs) tvingar dig att använda "rad-för-plågande-rad"-programmering när du hanterar ett MVF-fält, det är en tvåfas med bilagor eftersom du skulle behöva använda LoadFromFile eller
SaveToFile
metoder. Microsoft har en artikel med exempel om dessa metoder. Därför måste du interagera med filsystemet när du lägger till nya poster. Inte alltid önskvärt i alla situationer. Om vi nu kopierar från en tabell till en annan tabell kan vi undvika att studsa över filsystemet genom att göra något som:
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
Heliga looping, batman! Det är mycket kod, allt bara för att kopiera bilagor från en tabell till en annan. Även om vi inte studsar över filsystemet är det också väldigt långsamt. Enligt vår erfarenhet kan en tabell med 1000 poster som innehåller en enda bilaga ta minuter bara att bearbeta. Nu är det här ganska överdimensionerat när man tänker på storleken. Bordet med fästena är inte så stort. Faktum är att låt oss göra ett experiment. Låt oss se vad som händer om jag kopierar och klistrar in via datablad:
Så att kopiera och klistra är praktiskt taget omedelbart. Uppenbarligen är koden som används för att klistra in inte samma kod som vi skulle använda i VBA. Men vi är övertygade om att om vi kan göra det interaktivt så kan vi göra det i VBA också. Kan vi replikera hastigheten för interaktiv inklistring i VBA? Svaret visar sig vara ja, det kan vi!
Snabb fart med …. XML?
Överraskande nog är den metod som ger det snabbaste sättet att kopiera data, inklusive bilagor, via XML-filer. Jag kommer att erkänna att jag inte når XML-filer förutom som en lösning för begränsningar. I genomsnitt är XML-filer relativt långsamma till andra filformat, men i det här fallet har XML en stor fördel; det har inga problem att beskriva MVF:er. Låt oss skapa en XML-fil och undersöka vilka möjligheter vi får med att importera/exportera en XML-fil.
Efter den vanliga exportguidens dialogruta för att ställa in sökvägen för att spara XML-filen, kommer vi att få en dialogruta så här:
Om vi sedan klickar på knappen "Fler alternativ..." får vi den här dialogrutan istället:
Från den här dialogrutan ser vi några fler ledtrådar om vad som är möjligt; nämligen:
- Vi har möjlighet att exportera hela tabellen eller bara en delmängd av tabellen genom att använda ett filter
- Vi kan transformera XML-utdata.
- Vi kan beskriva schemat utöver innehållet i tabellen.
Jag tycker att det är bäst att bädda in schemat; standarden är att exportera den men som en separat fil. Det kan dock vara felbenäget och de kan glömma att inkludera XSD-filen med XML-filen. Detta kan ändras via schemafliken som visas:
Låt oss avsluta exporten och ta en snabb titt på den resulterande XML-filens data.
Observera att bilagorna beskrivs i Data
underträd och filinnehåll är base-64-kodat. Låt oss försöka importera XML-filen. Efter att ha gått igenom importguiden får vi den här dialogrutan:
Notera följande funktioner:
- Som med export har vi möjlighet att transformera XML.
- Vi kan kontrollera om strukturen, data eller båda ska importeras
Om vi sedan avslutar importen av XML-filen, upptäcker vi att den är lika snabb som kopieringen och klistra in.
Vi vet nu att det finns en bättre väg att kopiera flera poster med bilagor. Men i den här situationen vill vi göra detta programmatiskt, snarare än interaktivt. Kan vi göra samma sak som vi just gjorde? Återigen är svaret ja. Det finns flera sätt att göra samma sak men jag tror att den enklaste metoden är att använda de 3 nya metoderna som lades till i Applikationen
objekt sedan Access 2010:
ExporteraXML
metodTransformXML
metodImportXML
metod
Observera att ExportXML
metod stöder export från olika objekt. Men eftersom målet här är att kunna kopiera eller uppdatera en masse poster i en tabell med bifogade fält, är den bästa objekttypen för oss att använda en sparad fråga. Med en sparad fråga kan vi styra vilka rader som ska infogas eller uppdateras och vi kan även forma utdata. Om du tittar på schemadesignen för MSysResources
tabellen nedan:
Det finns ett potentiellt problem. När vi använder teman eller bilder hänvisar vi till objektet med namn, inte med ID. Men Namn
kolumnen är inte unik och är inte den primära nyckeln i tabellen. När vi lägger till eller uppdaterar poster vill vi därför matcha Name
kolumnen, inte Id
kolumn. Det betyder att när vi exporterar bör vi förmodligen inte inkludera Id
kolumnen och vi bör endast exportera den unika listan med Namn
för att säkerställa att resurserna inte plötsligt går från "Open.png" till "Close.png" eller något dumt.
Vi skapar sedan en fråga för att fungera som källa för de poster vi vill importera till MSysResources
tabell. Låt oss börja med denna SQL bara för att demonstrera nedfiltreringen till en delmängd av poster:
SELECT e.Data, e.Extension, e.Name, e.Type FROM Example AS e WHERE e.Name In ("blue","red","green");
Vi kommer sedan att spara det som qryResourcesExport
. Vi kan sedan skriva VBA-kod för att exportera XML:
Application.ExportXML _ ObjectType:=acExportQuery, _ DataSource:="qryResourcesExport", _ DataTarget:="C:\Path\to\Resources.xml", _ OtherFlags:=acEmbedSchema
Detta emulerar exporten som vi ursprungligen gjorde interaktivt.
Men om vi sedan importerar den resulterande XML-en har vi bara möjlighet att lägga till data till en befintlig tabell. Vi kan inte kontrollera vilken tabell den ska läggas till i; den kommer att hitta en tabell eller frågetabell med samma namn (t.ex. qryResourcesExport
och lägg till poster i den frågan. Om frågan är uppdateringsbar är det inga problem och den infogas i Exempel
som frågan baseras på. Men vad händer om källfrågan vi använder inte är uppdateringsbar eller kanske inte existerar? I båda fallen skulle vi inte kunna importera XML-filen som den är. Det kan antingen misslyckas att importera eller skapa en ny tabell med namnet qryResourcesExport
som inte hjälper oss. Och hur är det med fallet med kopiering av data från Exempel
till MSysResources
? Vi vill inte lägga till data till Exemplet
bord.
Det är där TransformXML
metoden kommer till undsättning. En fullständig diskussion om hur man skriver en XML-transformation ligger utanför räckvidden men du bör kunna hitta gott om resurser om hur man skriver en XSLT-formatmall för att beskriva transformationen. Det finns flera onlineverktyg du kan använda för att validera din XSLT också. Här är en. För det enkla fallet där vi bara vill styra vilken tabell XML-filen ska lägga till posterna i, kan du komma igång med denna XSLT-fil. Du kan sedan köra följande VBA-kod:
Application.TransformXML _ DataSource:="C:\Path\to\Resources.xml", _ TransformSource:="C:\Path\to\ResourcesTransform.xslt", _ OutputTarget:="C:\Path\to\Resources.xml", _ WellFormedXMLOutput:=True, _ ScriptOption:=acEnableScript
Vi kan ersätta den ursprungliga XML-filen med den transformerade XML-filen, som nu kommer att infogas i MSysResources
tabell snarare än in i (möjligen obefintlig fråga/tabell) qryResourcesExport
.
Vi måste sedan hantera uppdateringarna. Eftersom vi faktiskt lägger till nya poster och MSysResources
Tabellen inte har några begränsningar för dubblettnamnen, vi måste se till att alla befintliga poster med samma namn först tas bort. Detta kan åstadkommas genom att skriva en motsvarande fråga som så:
DELETE FROM MSysResources AS r WHERE r.Name In ("blue","red","green");
kör sedan den först innan du kör VBA-koden:
Application.ImportXML DataSource:="C:\Path\to\Resources.xml", ImportOptions:=acAppendData
Eftersom XML-filen transformerades, har ImportXML
metod kommer nu att infoga data i MSysResources
tabell istället för den ursprungliga frågan som vi använde med ExportXML
metod. Vi anger att den ska lägga till data i en befintlig tabell. Men om tabellen inte finns skapas den.
Och med det har vi uppnått en massuppdatering/insättning av tabellen med ett bifogat fält som är mycket snabbare i jämförelse med den ursprungliga VBA-koden för recordset- och child-recordset. Hoppas det hjälper! Om du behöver hjälp med att utveckla Access-applikationer, kontakta oss också!