Komprimierte Binärspeicherung

Dateien oder andere Binärobjekte in Datenbanktabellen zu speichern, ist bei vielen Access-Entwicklern verpönt. Das Hauptargument, das man dabei zu hören bekommt, ist das unverhältnismäßige Aufblähen der Datenbanken. Das greift jedoch nur dann, wenn die Objekte in einem OLE-Feld als OLE-Objekte abgespeichert werden. Denn dabei findet eine Konversion der Quelldateien in OLE-kompatible Dokumentenformate statt, die sie vergrößern. Dabei lassen sich die Dateien mit wirklich binärer Speicherung als BLOBs durchaus Platz sparend unterbringen – vor allem, wenn man sie dann noch zusätzlich mit einer Komprimierung versieht. Im folgenden Beitrag werden einige Anwendungsbeispiele und die zugrunde liegenden Techniken beleuchtet.

Wozu Binärobjekte

Das sicher am häufigsten nachgefragte Feature für Binärobjekte in Datenbanken ist die Anzeige von Bildern. Das ist auch nachvollziehbar, denn Entitäten in Datenbanken sind mit wahren Objekten der Realität verknüpft, die nun mal eine visuelle Entsprechung haben.

Ein Kunde hat ein Gesicht, ein Produkt ein Aussehen, eine Firma ein Logo. Das Konterfei eines Kunden also dem entsprechenden Kundendatensatz zuzuordnen, ein Produktbild ins Warenwirtschaftssystem zu bringen oder im Lieferantenformular Logos zu platzieren, macht durchaus Sinn.

Interessant wäre aber auch, etwa die Korrespondenz mit einem Kunden in Form von Texten, Word-Dokumenten, PDFs oder Excel-Dateien mit in eine Datenbank aufzunehmen, damit man direkt aus dem Kundenformular schnell darauf zugreifen kann.

üblicherweise speichert man in der Datenbank lediglich Verweise auf das Dateisystem und öffnet die Dateien zur Laufzeit mit der entsprechenden Anwendung. Das ist tatsächlich empfehlenswert, solange man sicher sein kann, dass sich Pfade nicht ändern, Dateien nicht verschoben werden und jeder Anwender überhaupt Zugriff auf die Verzeichnisse im Netzwerk hat.

Tritt einer dieser Fälle ein, dann ist man mit der Schwierigkeit konfrontiert, die Pfade in der Datenbank upzudaten oder Berechtigungen neu zu vergeben.

Das alles wäre überflüssig, wenn sich die Dateien gleich innerhalb der Datenbank befänden.

Wie kann man Binärdaten speichern

Es gibt je nach Access-Version bis zu drei übliche Möglichkeiten der Binärspeicherung, denen dieser Beitrag noch eine weitere hinzufügt – die Speicherung komprimierter Daten:

  • OLE-Objekte
  • BLOBs
  • Komprimierte BLOBs
  • Anlagen (Access 2007)

Ole-Felder sind für die erstgenannten drei Möglichkeiten der zu verwendende Datentyp des Binär-Containers. In Access 2007 wurde zusätzlich der neue Typ Attachment (Anlagefeld) eingeführt.

OLE-Objekte

OLE-Objekte sind sehr leicht zu handhaben, weil Access sich dabei sowohl um die Speicherung wie auch die Anzeige der Dateien kümmert. Sie lassen sich direkt in der Datenblattansicht in OLE-Feldern speichern und durch einfachen Doppelklick öffnen. Das Speichern geschieht etwa über den Eintrag Objekt einfügen… des Kontextmenüs eines leeren OLE-Felds. Im anschließend erscheinenden Dialog aktiviert man Aus Datei erstellen, wählt die Datei über den Durchsuchen-Button aus und deaktiviert außerdem das Kontrollkästchen Verknüpfen.

Bei dieser Vorgehensweise wird die Datei in jedem Fall in ein OLE-kompatibles Format konvertiert. Das bedeutet, dass sie mit Informationen zu einem auf dem Rechner verknüpften OLE-Server versehen wird. Ist dem Dateityp im System kein OLE-Server – das ist ein OLE-automationsfähiges Programm – zugeordnet, so wird ein Paket erstellt. Das ist ebenfalls ein OLE-Format – quasi ein Wrapper, ein Container für eine Datei. Beim Doppelklick wird diese Datei temporär wiederhergestellt und mit der verknüpften Anwendung geöffnet.

Bei einer dezidiert mit einem OLE-Server verknüpften Datei muss hingegen genau dieser OLE-Server auch auf dem Zielrechner installiert sein, wenn man der Fehlermeldung OLE-Server ist nicht vorhanden oder konnte nicht gefunden werden entgehen will. Speichert man etwa ein Word-Dokument in das OLE-Feld, dann muss auf dem Zielrechner ebenfalls Word installiert sein, wenn die Datei wieder aufgerufen werden soll. Würde das Dokument hingegen als Paket gespeichert, dann käme gegebenenfalls auch Wordpad zum öffnen in Betracht.

Man kann hier schon das wichtigste Problem im Zusammenhang mit OLE-Objekten ablesen: Ohne passenden OLE-Server keine Anzeige der Datei. Gerade bei Bilddateien kommt es häufig vor, dass diese über den OLE-Server Microsoft Photo Editor gespeichert werden, der aber auf einem Zielrechner nicht vorhanden ist. Ein JPG etwa kann selbst dann nicht geöffnet werden, wenn andere taugliche Bildanzeigen installiert sind.

Hinzu kommt, dass eine Datei als OLE-Objekt im OLE-Server in ein für ihn schnell lesbares Format umgewandelt und auch so abgespeichert wird. Ein JPG wird etwa häufig in ein bitmap-ähnliches Format konvertiert, das weitaus größer ist als die Ursprungsdatei. So kommt das Aufblähen der Datenbank zustande.

BLOBs

Beim Speichern als BLOBs (Binary Large Objects) hat man solche Probleme nicht. Hierbei wird die Datei Byte für Byte identisch mit dem Original im OLE-Feld gespeichert, dessen Kennzeichnung somit nicht mehr korrekt ist, weil es nun mit OLE überhaupt nichts mehr zu tun hat. Richtiger wäre die Bezeichnung LongVarBinary, wie sie ähnlich auch in SQL verwendet wird. Bei dieser Methode wächst die Datenbank entsprechend der Größe der eingelesenen Datei. Nachteil: Das Speichern und Auslesen muss per VBA-Routinen erfolgen, weil die Oberfläche von Access keine entsprechenden Mittel zur Verfügung stellt.

Komprimierte BLOBs

Das Speichern als komprimiertes BLOB funktioniert in gleicher Weise, nur mit dem Unterschied, dass die Datei zuvor mit geeigneten Algorithmen komprimiert wird. Dieses Komprimieren könnte man bereits im Dateisystem vollziehen, indem man die Datei zunächst mit einer Zip-Anwendung in eine Archiv-Datei konvertiert und erst dann einliest. Das ist jedoch überflüssig, weil es Möglichkeiten gibt, die Kompression rein im Speicher durchzuführen. In diesem Beitrag wird von solchen Möglichkeiten Gebrauch gemacht, wobei die Open-Source-Komponente zlib zum Einsatz kommt.

Tab. 1 zeigt den Speicherbedarf für die unterschiedlichen Methoden.

Beispieldatei

Größe

OLE-Objekt

BLOB

Komprimiertes BLOB

Tabelle 1: Speicherbedarf realistischer Beispieldaten bei unterschiedlichen Verfahren

Für welches der drei Verfahren man sich hiernach entscheidet, ist wohl klar: OLE-Speicherung vergrößert die Datenbank gegenüber komprimierter BLOB-Speicherung für die hier verwendeten Formate um den Faktor 54!

Solche Erfahrungen hat Microsoft dann wohl auch mit dazu bewegt, in Access 2007 den neuen Feldtyp Anlage (Attachment) einzuführen, dem ebenfalls eine komprimierte BLOB-Speicherung zu eigen ist. In Attachments können Sie Dateien mit dem gleichen Komfort über die Oberfläche speichern wie bisher in OLE-Feldern. Sie werden dabei aber nicht verändert, brauchen keinen OLE-Server und werden automatisch immer dann komprimiert, wenn es sinnvoll ist, also eine nennenswerte Verkleinerung der Daten zu erwarten ist. Das geschieht alles hinter den Kulissen.

Es stellt sich also die Frage: Ist mit Access 2007 das Thema erledigt Nach den Erfahrungen des Autors: Nicht ganz! Zum einen ist die Performance beim Ein- und Auslesen von Attachments deutlich geringer als das Gespann von BLOBs und ZLIB – etwa um den Faktor 1,5 -, zum anderen ist die Dateigröße etwas limitiert. Offenbar benötigt Access 2007 beim Einlesen von Dateien etwa gut das Doppelte der Dateigröße an RAM – den einen Teil für die Datei selbst, den anderen für das Ergebnis der Komprimierung.

So war es auf einem Rechner mit 700 MB freiem physischem RAM nicht möglich, Dateien größer als 220 MB in ein Anlagefeld zu speichern. Nicht, dass so große Dateien überhaupt in Datenbanken gespeichert werden sollten, aber bereits weit unter dieser Größe ließ die Performance erheblich zu wünschen übrig. Attachments kommen daher eher für kleinere Dateien in Betracht.

Ein anderer Punkt ist, dass es noch kaum Erfahrungen mit einer größeren Anzahl von Attachments in Access 2007-Datenbanken gibt und es nicht als sicher gelten kann, ob sich hier nicht die Probleme in puncto Stabilität wiederholen könnten, die es auch schon etwa mit Memofeldern gab.

BLOBS speichern

Binärdaten können über die Datenzugriffsbibliotheken DAO oder ADO in Tabellen gespeichert werden. DAO erwartet dabei zunächst ein Byte-Array als Zwischenspeicher für die Dateien, gefolgt von der Methode Field.AppendChunk, während ADO seit Version 2.5 ein Stream-Objekt mit der Methode LoadFromFile kennt, dessen Inhalt anschließend per Stream.Read einem Feldinhalt zugewiesen werden kann.

Das sieht einfacher aus und ist es auch, geht aber leider langsamer vonstatten als die DAO-Methode, sodass es keinen Grund gibt, hier ADO einzusetzen – außer Sie arbeiten mit Access-Projekten (ADP), in denen DAO nicht verwendet werden kann. Sie finden beide Verfahren im Modul mdlBLOBs der Beispieldatenbank binaer.mdb.

Die Routine aus Listing 1 verdeutlicht das Prinzip. Sie lädt eine Datei in ein Byte-Array und speichert dessen Inhalt anschließend in einem Tabellenfeld, das als OLE-Objekt definiert wurde.

Listing 1: Datei per DAO in ein Tabellenfeld einlesen

Sub SpeichereDatei()
     Dim bin() As Byte
     Dim rst As DAO.Recordset
     Open "c:\dateiname.dat" For Binary Access Read As #1
     ReDim bin(LOF(1))
     Get #1, , bin
     Close #1
     Set rst = CurrentDb.OpenRecordset("Tabelle1", dbOpenDynaset)
     rst.AddNew
     rst!Binaerfeld.AppendChunk bin()
     rst.Update
     rst.Close
End Sub

Sie finden ähnliche Routinen auch an anderen Stellen und in der Access-Hilfe. Möglicherweise fällt Ihnen dabei auf, dass die dort aufgeführten Codes etwas länger sind als in Listing 1. Das liegt daran, dass das Byte-Array häufig nicht in einem Rutsch in das Feld gespeichert wird, sondern in einer Schleife häppchenweise – daher auch der Begriff Chunk (Stück, Brocken).

Das kommt aus Zeiten, in denen die Rechner noch nicht mit gigabytegroßem RAM versehen waren und mit dem Speicher sorgsam umgegangen werden musste. Beim Aufteilen in kleinere Häppchen spart man Arbeitsspeicher ein. Heutzutage ist das nicht mehr notwendig und das Array kann komplett zugewiesen werden, sofern Sie nicht auf die Idee kommen sollten, Ihre Sammlung von Video-Dateien in der Datenbank zu speichern …

Erstaunlich einfach funktioniert das Speichern des Arrays übrigens, wenn Sie ein gebundenes Formular verwenden, dessen Datenherkunft das OLE-Feld enthält. Dann reduziert sich der Code auf die Prozedur aus Listing 2.

Listing 2: Datei per DAO in ein Formularfeld einlesen

Private Sub SpeichereDatei()
     Dim bin() As Byte
     Open "c:\dateiname.dat" For Binary Access Read As #1
     ReDim bin(LOF(1))
     Get #1, , bin
     Close #1
     Me!Binaerfeld.Value = bin()
End Sub

Es wird also schlichtweg das Byte-Array dem Tabellenfeld des Formulars verabreicht, ohne dass die Methode AppendChunk zum Einsatz kommen müsste. Ein Steuerelement für das OLE-Feld müssen Sie dabei erst gar nicht vorsehen.

Der Vollständigkeit halber finden Sie in Listing 3 noch eine zu Listing 1 analoge Routine, die ADO verwendet.

Listing 3: Datei per ADO 2.5 oder höher in ein Tabellenfeld einlesen

Sub SpeichereDatei()
     Dim rst As New ADODB.Recordset
     Dim rsStm As New ADODB.Stream
     rst.Open "Tabelle1", CurrentProject.Connection, _
     adOpenDynamic, adLockPessimistic
     rst.AddNew
     With rsStm
         .Type = adTypeBinary
         .Open
         .LoadFromFile "c:\dateiname.dat"
         rst!Binaerfeld.Value = .Read
         .Close
     End With
     rst.Close
End Sub

BLOBs auslesen

Was bei einer als OLE-Objekt im Tabellenfeld gespeicherten Datei per Doppelklick geht oder im Falle eines gebundenen OLE-Felds im Formular sogar automatisch passiert, das erfordert bei BLOBs wieder etwas VBA-Code: Das Auslesen und Anzeigen der Datei.

Dabei erfolgt unter DAO wie ADO jeweils der umgekehrte Vorgang wie beim Einlesen: Unter DAO wird zunächst ein Byte-Array mit der GetChunk-Methode eines Recordset-Feldes befüllt und anschließend als Datei abgespeichert.

Diese kann dann mit der für sie verknüpften Anwendung geöffnet werden. Hierfür können die VBA-Anweisung Shell oder die API-Funktion ShellExecute verwendet werden. Unter ADO wird zunächst ein Stream-Objekt mit dem Inhalt des Binärfelds gefüllt und anschließend mit der Methode SaveToFile in eine Datei gespeichert.

Ein passender DAO-Code sähe etwa wie in Listing 4 aus und die Routine, welche die mit der Prozedur aus Listing 2 in ein Formularfeld kopierte Datei wiederherstellt, finden Sie in Listing 5.

Listing 4: Datei per DAO aus Binärfeld wiederherstellen und anzeigen

Sub DateiWiederherstellen()
     Dim bin() As Byte
     Dim lSize As Long
     Dim rst As DAO.Recordset
     Set rst = CurrentDb.OpenRecordset("SELECT Binaerfeld " _
& "FROM Tabelle1 WHERE ID=1", dbOpenDynaset)
     lSize = rst!Binaerfeld.FieldSize - 1
     Redim bin(lSize)
     bin = rst(0).GetChunk(0, lSize)
     Open "c:\dateiname.dat" For Binary Access Write As #1
     Put #1, , bin()
     Close #1
     rst.Close
     ShellExecute 0, "open", "c:\dateiname.dat", vbNullString, _
CurDir, 1
End Sub

Listing 5: Datei aus Formularfeld wiederherstellen und anzeigen

Private Sub ZeigeDatei()
     Dim bin() As Byte
     bin = Me!Binaerfeld.Value
     Open "c:\dateiname.dat" For Binary Access Read As #1
     Put #1, , bin
     Close #1
     ShellExecute 0, "open", "c:\dateiname.dat", vbNullString, _
CurDir, 1
End Sub

Fehlt noch das Pendant zu der Routine aus Listing 3, diesmal mit ADO (s. Listing 6).

Listing 6: Datei per ADO 2.5 oder höher aus Tabellenfeld wiederherstellen und anzeigen

Sub DateiWiederherstellen()
     Dim rst As New ADODB.Recordset
     Dim rsStm As New ADODB.Stream
     rst.Open "SELECT Binaerfeld FROM Tabelle1 WHERE ID=1", _
     CurrentProject.Connection, adOpenDynamic, adLockPessimistic
     With rsStm
         .Type = adTypeBinary
         .Open
         .Write rst(0).Value
         .SaveToFile "c:\dateiname.dat", adSaveCreateOverWrite
         .Close
     End With
     rst.Close
     ShellExecute 0, "open", "c:\dateiname.dat", vbNullString, CurDir, 1
End Sub

Die Deklaration der eingesetzten API-Funktion ShellExecute, die zum Anzeigen einer beliebigen Datei benutzt werden kann, finden Sie in der Beispieldatenbank binaer.mdb im Modul mdlHelper. Die Funktion startet automatisch die systemweit mit einer Dateiendung verknüpfte Anwendung.

BLOB-Module

Die zuvor aufgeführten Codes sind ziemlich rudimentär und veranschaulichen lediglich die grundsätzlichen Funktionsweisen. In den Beispieldatenbanken binaer.mdb und binaer.accdb sind ausführlichere Module enthalten, die allgemein verwendbare Routinen beherbergen.

Das Modul mdlBLOBs zeigt Funktionen, denen Dateinamen, Tabellennamen, Feldbezeichnungen als Parameter übergeben werden können, um Dateien auszulesen und wiederherzustellen.

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar