Große Dateien aus einer Datenbank lesen und zum Download bereitstellen.
in dem Beitrag "Große Dateien in hochladen und in eine Datenbank schreiben" habe ich beschrieben, wie man Dateien performant in die Datenbank schreiben kann. Umgekehrt sollten die Werte ja auch wieder ausgelesen werden. Auch hier gibt es in der MSDN wieder einen Artikel dazu: Abrufen von BLOB-Werten aus einer Datenbank
Unten habe ich ein Beispielprojekt verlinkt in dem gezeigt wird, wie die vom Microsoft beschriebene Methode in einer ASP.NET Anwendung genutzt werden kann, um Dateien aus der Datenbank auszulesen und über einen HttpHandler zum Download bereit zu stellen.
Das Auslesen der Daten ist im Vergleich zum schreiben wesentlich einfacher und mit reinen ADO.NET Möglichkeiten umzusetzen. Der Trick hierbei ist, dass man dem DataReader (mit Hilfe des CommandBehaviors "SequentialAccess") sagen kann, dass er die Daten nicht komplett aus der Datenbank laden soll, sondern als Stream:
SqlDataReader myReader = comd.ExecuteReader(CommandBehavior.SequentialAccess);
Anschließend könne die Binärdaten mit GetBytes() stückchenweise über den DataReader aus der Datenbank ausgelesen werden:
int bufferLen = 1024;
byte[] buffer = new byte[bufferLen];
long retval;
long offset = 0;
[...]
if (myReader.Read())
{
using (BinaryWriter bw = new BinaryWriter(OutputStream))
{
retval = myReader.GetBytes(0, offset, buffer, 0, bufferLen);
while (retval == bufferLen)
{
bw.Write(buffer);
bw.Flush();
offset += bufferLen;
retval = myReader.GetBytes(0, offset, buffer, 0, bufferLen);
}
bw.Write(buffer, 0, (int)retval - 1);
bw.Flush();
bw.Close();
}
}
Auch hier bestimmt die Puffergröße (bufferLen) nicht nur Größe der Datenpakete, sondern 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.
Genauso wie in dem vorhergehenden Artikel aus dem vorhandenen Stream partiell gelesen wird, wird hier Stück für Stück in einen Steam geschrieben. Der Stream kann dann der aktuelle OutpuStream eines generischen HttpHandlers sein. Die ProzessRequest Methode des generischen HttpHandlers könnte dann wie folgt aussehen:
public void ProcessRequest(HttpContext context)
{
int id = 0;
if (!int.TryParse(context.Request.QueryString[id], out id))
return;
MyFile file = BinaryManager.ReadFile(id);
if (file == null)
return;
context.Response.ContentType = file.ContentType;
context.Response.AddHeader("Content-Disposition",
"attachment; filename=" +
System.IO.Path.GetFileName(file.FileName));
context.Response.AddHeader("Content-Length", file.FileSize.ToString());
BinaryManager.ReadFromDB(context.Response.OutputStream, id);
}
Übrigenz macht es Sinn jede große Datei, egal ob sie aus der Datenbank kommt oder nicht, in so einer Schleife, stückchenweise in den ResponseStream zu schreiben. Der Browser muss auf diese Art nicht zu lange auf eine erste Reaktion vom Webserver warten. Der Download funktioniert dan schneller und flüssiger.
Beispielprojekt: download (228KB)