Mehr von Jürgen Gutsch

Mehr von Jürgen Gutsch

Empfehlungen von Jürgen Gutsch

Blog-Empfehlungen von Jürgen Gutsch

Willkommen bei ASP.NET Zone. Anmelden | Registrieren | Hilfe

Jürgen Gutsch

ASP.NET und mehr...

News

Große Dateien hochladen und in eine Datenbank schreiben

Auch wenn es meistens nicht Ideal ist Dateien in der Datenbank abzulegen gibt es dennoch hin und wieder Fälle in denen es dennoch sinnvoll ist.

Als häufigster Grund um Dateien in der Datenbank abzulegen wird immer wieder einfacheres Backup der Daten genannt. En weiterer Grund ist, dass man über sog. IFilter auch die Volltextsuche des SQL Server über die Dokumente laufen lassen kann.

Die Nachteile liegen auf der Hand: schlechtere Performance, Fehleranfälligkeit und Mehraufwand.

Microsoft beschreibt in dem Artikel "Sparsames Verwenden von Ressourcen beim Schreiben von BLOB-Werten in SQL Server" wie man Dateien in einer Datenbank ablegt ohne die Ressourcen zu stark zu beanspruchen.

Der Trick an der Sache ist, die Daten immer in kleinen Stückchen aus dem Stream auszulesen und in die Datenbank zu schreiben.

Unten habe ich ein Beispielprojekt verlinkt in dem gezeigt wird, wie die vom Microsoft beschriebene Methode in einer ASP.NET Anwendung mit einem FileUplad angewendet werden kann.

In dem Beispielprojekt habe ich eine Klasse mit dem Namen "BinaryManager" angelegt, die statische Methoden für die Datenbankoperationen enthält.

Das stückchenweise Schreiben in die Datenbank löst Microsoft mit einem Pointer auf die Binärspalte des aktuellen Datensatzes. Mehr zu den Pointern: TEXTPTR (Hinweis: Microsoft schreibt hier das diese Funktion in Zukunft im SQL Server nicht mehr unterstützt wird. So wie es aussieht, scheint es aber im 2008er noch enthalten zu sein. Getestet habe ich es mit dem 2005er und mit dem 2000er schon des Öfteren angewendet.)

INSERT INTO allfiles (filename, contenttype, filesize, filecontent) VALUES(@filename, @contenttype, @filesize, 0x0);
SELECT @Identity = SCOPE_IDENTITY();
SELECT @Pointer = TEXTPTR(filecontent) FROM allfiles WHERE id = @Identity

Hier wird also erst mal der Datensatz mit den nötigen Infos und ohne die Datei angelegt. In die Binärspalte wird als Standardwert NULL geschrieben. Anschließend wird die neue ID ermittelt und anhand dieser der Pointer erstellt. Sowohl die neue ID als auch der Pointer werden per SqlParameter ausgelesen:

SqlParameter idParm = comd.Parameters.Add("@Identity", SqlDbType.Int);
idParm.Direction = ParameterDirection.Output;
SqlParameter ptrParm = comd.Parameters.Add("@Pointer", SqlDbType.Binary, 16);
ptrParm.Direction = ParameterDirection.Output;

Der Pointer wird dann zusammen mit dem Stream an eine weitere Methode übergeben in der die eigentliche Arbeit stattfindet.

Mit Hilfe der T-SQL Funktion UPDATETEXT werden jetzt immer nur 1024 Bytes auf einmal in die Datenbank geschrieben, so lange bis der BinaryReader am Ende des Streams angekommen ist.

int bufferLen = 1024;
[...]
using (BinaryReader br = new BinaryReader(InputStream))

    byte[] buffer = br.ReadBytes(bufferLen);
    int offset = 0; 
    while (buffer.Length > 0)
    {
        binParm.Value = buffer;
        command.ExecuteNonQuery();
        offset += bufferLen;
        offsetParm.Value = offset;
        buffer = br.ReadBytes(bufferLen);
    } 
    br.Close();
    InputStream.Close();
}

Die Puffergröße (bufferLen) bestimmt nicht nur Größe der Datenpakete und somit auch die Anzahl der Schleifendurchläufe. Ein niedriger Wert erhöht die Schleifendurchläufe reduziert den Speicherbedarf, erhöht aber die Prozessorlast. Umgekehrt verringert ein höherer Wert die Prozessorlast, erhöht aber den Speicherbedarf. Man sollte also mit der Größe spielen um den perfekten Mittelwert zu finden.

Der eigentliche Upload ist dann nur noch ein Kinderspiel:

protected void Button1_Click(object sender, EventArgs e)
{
    int newid = BinaryManager.SaveToDB(
        this.FileUpload1.PostedFile.InputStream,
        this.FileUpload1.PostedFile.FileName,
        this.FileUpload1.PostedFile.ContentType,
        this.FileUpload1.PostedFile.ContentLength);
}

Beispielprojekt: download (228KB)

Posted: Samstag, 22. November 2008 09:30 von Jürgen Gutsch

Kommentare

Peter Bucher sagte:

Salute Jürgen

Super, kann ich vielleicht mal gebrauchen :)

# November 23, 2008 03:13

win.ini sagte:

Hi Jürgen,

Ich sehe aber da noch ein Problem:

Es gibt da auch Leute die durchaus gaaaanz große Dateien Archivieren wollen. Sagen wir mal 500 MB bis 1GB. Den Upload kann man dann in der web.config entsprechend hoch setzen. Aber die Methode "FileUpload1.PostedFile.InputStream" frisst dann Brot im Ram des Servers. Die Datei wird erst in den Ram geladen und dann kann man sie bequem Byteweise auslesen. Aber bei großen Dateien blockiert man so gerne den Speicher und andere User wunder sich, warum die Webseite so langsam ist oder der Request abbricht.

Noch eleganter und nicht trivial ist das auslesend der Headerinformationen und der Lokalisierung der Binären Daten im Header, Stichwort "Boundery". Dann kommt man um eine HttprequestWorker (weiß nicht genau, ob der so heißt??) nicht drum herum. Wenn man dann die Stelle gefunden hat, wo die Daten sind, kann man die auslesen und Byteweise in den Ram laden und dann wieder byteweise in die Datenbank (Deiner Methode). Ich weiß leider auch nicht genau wie ich das lösen soll. Bin aber bei der Sache dran. Das ist nicht so einfach ....

Aber das ist ein schönes Beispiel hier, werde das bestimmt auch mal gebrauchen!! :)

# Dezember 3, 2008 11:38
Anonyme Kommentare sind nicht zugelassen