Speichern Sie eine Datei in der Datenbank, konvertieren Sie sie einfach in ein Byte-Array?

73

Ist das Konvertieren einer Datei in ein Byte-Array der beste Weg, um ein beliebiges Dateiformat auf einer binären Spalte der Festplatte oder Datenbank var zu speichern?

Wenn jemand eine .gif- oder .doc / .docx- oder .pdf-Datei speichern möchte, kann ich sie dann einfach in ein Bytearray-UFT8 konvertieren und als Bytestrom in der Datenbank speichern?

Blankman
quelle
Das Konvertieren in ein Byte-Array bedeutet natürlich, dass das Ganze in den [virtuellen] Speicher geladen wird.
Steve Smith

Antworten:

157

Da nicht erwähnt wird, welche Datenbank Sie meinen, gehe ich von SQL Server aus. Die folgende Lösung funktioniert sowohl für 2005 als auch für 2008.

Sie müssen eine Tabelle mit VARBINARY(MAX)einer der Spalten erstellen . In meinem Beispiel habe ich eine Tabelle Raportymit einer Spalte RaportPlikals VARBINARY(MAX)Spalte erstellt.

Methode zum Einfügen filein die Datenbank vondrive :

public static void databaseFilePut(string varFilePath) {
    byte[] file;
    using (var stream = new FileStream(varFilePath, FileMode.Open, FileAccess.Read)) {
        using (var reader = new BinaryReader(stream)) {
            file = reader.ReadBytes((int) stream.Length);       
        }          
    }
    using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetails))
    using (var sqlWrite = new SqlCommand("INSERT INTO Raporty (RaportPlik) Values(@File)", varConnection)) {
        sqlWrite.Parameters.Add("@File", SqlDbType.VarBinary, file.Length).Value = file;
        sqlWrite.ExecuteNonQuery();
    }
}

Diese Methode dient zum Abrufen fileaus der Datenbank und zum Speichern aufdrive :

public static void databaseFileRead(string varID, string varPathToNewLocation) {
    using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetails))
    using (var sqlQuery = new SqlCommand(@"SELECT [RaportPlik] FROM [dbo].[Raporty] WHERE [RaportID] = @varID", varConnection)) {
        sqlQuery.Parameters.AddWithValue("@varID", varID);
        using (var sqlQueryResult = sqlQuery.ExecuteReader())
            if (sqlQueryResult != null) {
                sqlQueryResult.Read();
                var blob = new Byte[(sqlQueryResult.GetBytes(0, 0, null, 0, int.MaxValue))];
                sqlQueryResult.GetBytes(0, 0, blob, 0, blob.Length);
                using (var fs = new FileStream(varPathToNewLocation, FileMode.Create, FileAccess.Write)) 
                    fs.Write(blob, 0, blob.Length);
            }
    }
}

Diese Methode wird fileaus der Datenbank abgerufen und wie folgt ausgedrücktMemoryStream :

public static MemoryStream databaseFileRead(string varID) {
    MemoryStream memoryStream = new MemoryStream();
    using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetails))
    using (var sqlQuery = new SqlCommand(@"SELECT [RaportPlik] FROM [dbo].[Raporty] WHERE [RaportID] = @varID", varConnection)) {
        sqlQuery.Parameters.AddWithValue("@varID", varID);
        using (var sqlQueryResult = sqlQuery.ExecuteReader())
            if (sqlQueryResult != null) {
                sqlQueryResult.Read();
                var blob = new Byte[(sqlQueryResult.GetBytes(0, 0, null, 0, int.MaxValue))];
                sqlQueryResult.GetBytes(0, 0, blob, 0, blob.Length);
                //using (var fs = new MemoryStream(memoryStream, FileMode.Create, FileAccess.Write)) {
                memoryStream.Write(blob, 0, blob.Length);
                //}
            }
    }
    return memoryStream;
}

Diese Methode wird in die MemoryStreamDatenbank gestellt:

public static int databaseFilePut(MemoryStream fileToPut) {
        int varID = 0;
        byte[] file = fileToPut.ToArray();
        const string preparedCommand = @"
                    INSERT INTO [dbo].[Raporty]
                               ([RaportPlik])
                         VALUES
                               (@File)
                        SELECT [RaportID] FROM [dbo].[Raporty]
            WHERE [RaportID] = SCOPE_IDENTITY()
                    ";
        using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetails))
        using (var sqlWrite = new SqlCommand(preparedCommand, varConnection)) {
            sqlWrite.Parameters.Add("@File", SqlDbType.VarBinary, file.Length).Value = file;

            using (var sqlWriteQuery = sqlWrite.ExecuteReader())
                while (sqlWriteQuery != null && sqlWriteQuery.Read()) {
                    varID = sqlWriteQuery["RaportID"] is int ? (int) sqlWriteQuery["RaportID"] : 0;
                }
        }
        return varID;
    }

Viel Spaß beim Codieren :-)

MadBoy
quelle
2
Ich denke, viele Leute kommen von einer Suchmaschine zu dieser Antwort und sind hilfreich für sie, aber sie beantworten die Frage nicht. OP fragte, ob es bessere Optionen gäbe und wollte einen Vergleich, denke ich.
Bugybunny
3
Diese Antwort wurde im Jahr 2010 gegeben. Sie können gerne weitere Antworten hinzufügen, die die Lücke füllen.
MadBoy
Ich erhalte die Fehlermeldung "Der Name 'Gebietsschema' existiert im aktuellen Kontext nicht". Was soll ich unter "using" hinzufügen?
Ian
11

Während Sie Dateien auf diese Weise speichern können, hat dies erhebliche Nachteile:

  • Die meisten DBs sind nicht für riesige Mengen an Binärdaten optimiert, und die Abfrageleistung verschlechtert sich häufig dramatisch, wenn die Tabelle selbst bei Indizes aufgebläht wird. (SQL Server 2008 mit dem Spaltentyp FILESTREAM ist die Ausnahme von der Regel.)
  • Die DB-Sicherung / Replikation wird extrem langsam.
  • Es ist viel einfacher, mit einem beschädigten Laufwerk mit 2 Millionen Images umzugehen - ersetzen Sie einfach die Festplatte im RAID - als mit einer beschädigten DB-Tabelle.
  • Wenn Sie versehentlich ein Dutzend Images in einem Dateisystem löschen, können Ihre Operations-Mitarbeiter diese recht einfach aus einem Backup ersetzen. Da der Tabellenindex im Vergleich dazu winzig ist, kann er schnell wiederhergestellt werden. Wenn Sie versehentlich ein Dutzend Bilder in einer riesigen Datenbanktabelle löschen, müssen Sie lange und schmerzhaft warten, bis die Datenbank aus der Sicherung wiederhergestellt ist, wodurch Ihr gesamtes System in der Zwischenzeit gelähmt wird.

Dies sind nur einige der Nachteile, die ich mir auf den Kopf stellen kann. Für kleine Projekte kann es sich lohnen, Dateien auf diese Weise zu speichern, aber wenn Sie Software für Unternehmen entwerfen, würde ich dringend davon abraten.

Dan Story
quelle
8
Ihre Antwort lautet also "nicht". Entschuldigung, aber das ist keine Antwort - besonders wenn es ihre Anforderung sein könnte, dies zu tun. Das OP fragt "wie ist es am besten", es zu tun, nicht "sollte" sie.
Vapcguy
7

Es hängt wirklich vom Datenbankserver ab.

Beispielsweise unterstützt SQL Server 2008 einen FILESTREAMDatentyp für genau diese Situation.

Wenn Sie a verwenden MemoryStream, wird eine ToArray()Methode verwendet, die in a konvertiert wird. byte[]Dies kann zum Auffüllen eines varbinaryFelds verwendet werden.

Oded
quelle
3

Ich werde beschreiben, wie ich Dateien in SQL Server und Oracle gespeichert habe. Es hängt in erster Linie davon ab, wie Sie die Datei erhalten, wie Sie ihren Inhalt erhalten, und es hängt davon ab, welche Datenbank Sie für den Inhalt verwenden, in dem Sie sie speichern, und wie Sie sie speichern. Dies sind 2 separate Datenbankbeispiele mit 2 separaten Methoden zum Abrufen der von mir verwendeten Datei.

SQL Server

Kurze Antwort: Ich habe eine Base64-Byte-Zeichenfolge verwendet, die ich in eine konvertiert byte[]und in einem varbinary(max)Feld gespeichert habe .

Lange Antwort:

Angenommen, Sie laden über eine Website <input id="myFileControl" type="file" />hoch , verwenden also ein Steuerelement oder React DropZone. Um die Datei zu erhalten, machen Sie so etwas wie var myFile = document.getElementById("myFileControl")[0];oder myFile = this.state.files[0];.

Von dort würde ich die base64-Zeichenfolge mit Code hier erhalten: Konvertiere input = file in byte array (benutze Funktion UploadFile2).

Dann würde ich diesen String, den Dateinamen ( myFile.name) und den Typ ( myFile.type) in ein JSON-Objekt bekommen:

var myJSONObj = {
    file: base64string,
    name: myFile.name,
    type: myFile.type,
}

und senden Sie die Datei mit XMLHttpRequest an ein MVC-Server-Backend, wobei Sie einen Inhaltstyp angeben application/json: xhr.send(JSON.stringify(myJSONObj);. Sie müssen ein ViewModel erstellen, um es zu binden:

public class MyModel
{
    public string file { get; set; }
    public string title { get; set; }
    public string type { get; set; }
}

und geben Sie [FromBody]MyModel myModelObjals übergebenen Parameter an:

[System.Web.Http.HttpPost]  // required to spell it out like this if using ApiController, or it will default to System.Mvc.Http.HttpPost
public virtual ActionResult Post([FromBody]MyModel myModelObj)

Anschließend können Sie dies zu dieser Funktion hinzufügen und mit Entity Framework speichern:

MY_ATTACHMENT_TABLE_MODEL tblAtchm = new MY_ATTACHMENT_TABLE_MODEL();
tblAtchm.Name = myModelObj.name;
tblAtchm.Type = myModelObj.type;
tblAtchm.File = System.Convert.FromBase64String(myModelObj.file);
EntityFrameworkContextName ef = new EntityFrameworkContextName();
ef.MY_ATTACHMENT_TABLE_MODEL.Add(tblAtchm);
ef.SaveChanges();

tblAtchm.File = System.Convert.FromBase64String(myModelObj.file); die operative Linie sein.

Sie benötigen ein Modell zur Darstellung der Datenbanktabelle:

public class MY_ATTACHMENT_TABLE_MODEL 
{
    [Key]
    public byte[] File { get; set; }  // notice this change
    public string Name { get; set; }
    public string Type { get; set; }
}

Dadurch werden die Daten in einem varbinary(max)Feld als gespeichert byte[]. Nameund Typewaren nvarchar(250)und nvarchar(10)jeweils. Sie können die Größe einschließen, indem Sie sie als intSpalte & MY_ATTACHMENT_TABLE_MODELas zu Ihrer Tabelle public int Size { get; set;}hinzufügen und in der tblAtchm.Size = System.Convert.FromBase64String(myModelObj.file).Length;obigen Zeile hinzufügen .

Orakel

Kurze Antwort: Konvertieren Sie es in a byte[], weisen Sie es einem zu OracleParameter, fügen Sie es Ihrem hinzu OracleCommandund aktualisieren Sie das BLOBFeld Ihrer Tabelle unter Verwendung eines Verweises auf den ParameterNameWert des Parameters ::BlobParameter

Lange Antwort: Als ich dies für Oracle getan habe, habe ich ein verwendet OpenFileDialogund die Bytes / Datei-Informationen auf folgende Weise abgerufen und gesendet:

byte[] array;
OracleParameter param = new OracleParameter();
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.Filter = "Image Files (*.jpg, *.jpeg, *.jpe)|*.jpg;*.jpeg;*.jpe|Document Files (*.doc, *.docx, *.pdf)|*.doc;*.docx;*.pdf"
if (dlg.ShowDialog().Value == true)
{
    string fileName = dlg.FileName;
    using (FileStream fs = File.OpenRead(fileName)
    {
        array = new byte[fs.Length];
        using (BinaryReader binReader = new BinaryReader(fs))
        {
            array = binReader.ReadBytes((int)fs.Length);
        }

        // Create an OracleParameter to transmit the Blob
        param.OracleDbType = OracleDbType.Blob;
        param.ParameterName = "BlobParameter";
        param.Value = array;  // <-- file bytes are here
    }
    fileName = fileName.Split('\\')[fileName.Split('\\').Length-1]; // gets last segment of the whole path to just get the name

    string fileType = fileName.Split('.')[1];
    if (fileType == "doc" || fileType == "docx" || fileType == "pdf")
        fileType = "application\\" + fileType;
    else
        fileType = "image\\" + fileType;

    // SQL string containing reference to BlobParameter named above
    string sql = String.Format("INSERT INTO YOUR_TABLE (FILE_NAME, FILE_TYPE, FILE_SIZE, FILE_CONTENTS, LAST_MODIFIED) VALUES ('{0}','{1}',{2},:BlobParamerter, SYSDATE)", fileName, fileType, array.Length);

    // Do Oracle Update
    RunCommand(sql, param);
}

Und innerhalb des Oracle-Updates, das mit ADO erstellt wurde:

public void RunCommand(string sql, OracleParameter param)
{
    OracleConnection oraConn = null;
    OracleCommand oraCmd = null;
    try
    {
        string connString = GetConnString();
        oraConn = OracleConnection(connString);
        using (oraConn)
        {
            if (OraConnection.State == ConnectionState.Open)
                OraConnection.Close();

            OraConnection.Open();

            oraCmd = new OracleCommand(strSQL, oraConnection);

            // Add your OracleParameter
            if (param != null)
                OraCommand.Parameters.Add(param);

            // Execute the command
            OraCommand.ExecuteNonQuery();
        }
    }
    catch (OracleException err)
    {
       // handle exception 
    }
    finally
    {
       OraConnction.Close();
    }
}

private string GetConnString()
{
    string host = System.Configuration.ConfigurationManager.AppSettings["host"].ToString();
    string port = System.Configuration.ConfigurationManager.AppSettings["port"].ToString();
    string serviceName = System.Configuration.ConfigurationManager.AppSettings["svcName"].ToString();
    string schemaName = System.Configuration.ConfigurationManager.AppSettings["schemaName"].ToString();
    string pword = System.Configuration.ConfigurationManager.AppSettings["pword"].ToString(); // hopefully encrypted

    if (String.IsNullOrEmpty(host) || String.IsNullOrEmpty(port) || String.IsNullOrEmpty(serviceName) || String.IsNullOrEmpty(schemaName) || String.IsNullOrEmpty(pword))
    {
        return "Missing Param";
    }
    else
    {
        pword = decodePassword(pword);  // decrypt here
        return String.Format(
           "Data Source=(DESCRIPTION =(ADDRESS = ( PROTOCOL = TCP)(HOST = {2})(PORT = {3}))(CONNECT_DATA =(SID = {4})));User Id={0};Password={1};",
           user,
           pword,
           host,
           port,
           serviceName
           );
    }
}

Und der Datentyp für die FILE_CONTENTSSpalte war BLOB, FILE_SIZEwar NUMBER(10,0), LAST_MODIFIEDwar DATEund der Rest war NVARCHAR2(250).

vapcguy
quelle
2

Ja, im Allgemeinen ist der beste Weg, eine Datei in einer Datenbank zu speichern, das Speichern des Byte-Arrays in einer BLOB-Spalte. Möglicherweise möchten Sie in einigen Spalten zusätzlich die Metadaten der Datei wie Name, Erweiterung usw. speichern.

Es ist nicht immer eine gute Idee, Dateien in der Datenbank zu speichern. Beispielsweise wächst die Datenbankgröße schnell, wenn Sie Dateien in der Datenbank speichern. Das hängt jedoch alles von Ihrem Nutzungsszenario ab.

driis
quelle
Blobgilt für Oracle, nicht für SQL Server.
Vapcguy
@vapcguy Die Frage (und diese Antwort) geben keine bestimmte Datenbank an.
Steve Smith
@SteveSmith Nicht wahr. Sie fragten nach einem "var binary" -Feld. Diese existieren nicht in Oracle, sondern in SQL Server.
Vapcguy
1
Unter der Annahme, dass OP speziell "varbinary" bedeutet, existieren diese auch in Maria / MySQL. Ich kann jedoch davon ausgehen, dass OP nach den praktischen Aspekten und allgemeinen Vor- und Nachteilen der Speicherung von Binärdaten in einer Datenbankmarke gefragt hat.
Steve Smith
1
@SteveSmith Interessant. Wusste nichts davon - danke.
Vapcguy
2

Bestätigung, dass ich die von MadBoy gepostete und von Otiel auf MS SQL Server 2012 und 2014 bearbeitete Antwort zusätzlich zu den zuvor mit varbinary (MAX) -Spalten aufgelisteten Versionen verwenden konnte.

Wenn Sie sich fragen, warum Sie im SQL Server-Tabellen-Designer nicht "Datestream" (in einer separaten Antwort angegeben) als Datentyp verwenden können oder warum Sie den Datentyp einer Spalte mit T-SQL nicht auf "Filestream" setzen können, liegt dies daran, dass FILESTREAM ein Speicher ist Attribut des Datentyps varbinary (MAX). Es ist kein Datentyp für sich.

Lesen Sie diese Artikel zum Einrichten und Aktivieren von FILESTREAM in einer Datenbank: https://msdn.microsoft.com/en-us/library/cc645923(v=sql.120).aspx

http://www.kodyaz.com/t-sql/default-filestream-filegroup-is-not-available-in-database.aspx

Nach der Konfiguration kann eine filestream-fähige varbinary (max) -Spalte wie folgt hinzugefügt werden:

ALTER TABLE TableName

ADD ColumnName varbinary (max) FILESTREAM NULL

GEHEN

Brian C.
quelle
1

Welche Datenbank verwenden Sie? Normalerweise speichert man keine Dateien in einer Datenbank, aber ich denke, SQL 2008 unterstützt dies ...

Eine Datei besteht aus Binärdaten, daher spielt UTF 8 hier keine Rolle.

UTF 8 ist wichtig, wenn Sie versuchen, eine Zeichenfolge in ein Byte-Array zu konvertieren ... nicht in eine Datei in ein Byte-Array.

Peter
quelle