web-dev-qa-db-fra.com

Enregistrer un fichier dans la base de données, convertissez-le simplement en tableau d'octets?

La conversion d'un fichier en tableau d'octets constitue-t-elle le meilleur moyen de sauvegarder TOUT format de fichier sur une colonne binaire de disque ou de base de données?

Donc, si quelqu'un veut enregistrer un fichier .gif ou .doc/.docx ou .pdf, puis-je le convertir en UT8 bytearray et l'enregistrer sous la base de données sous forme de flux d'octets?

64
Blankman

Puisqu'il n'est pas mentionné quelle base de données tu veux dire je suppose que SQL Server. La solution ci-dessous fonctionne pour 2005 et 2008.

Vous devez créer un tableau avec VARBINARY(MAX) comme l'une des colonnes. Dans mon exemple, j'ai créé la table Raporty avec la colonne RaportPlik étant la colonne VARBINARY(MAX).

Méthode pour mettre file dans la base de données à partir de drive:

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();
    }
}

Cette méthode consiste à extraire file de la base de données et à l'enregistrer sur drive:

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);
            }
    }
}

Cette méthode consiste à extraire file de la base de données et à la placer sous la forme MemoryStream:

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;
}

Cette méthode consiste à mettre MemoryStream dans la base de données:

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;
    }

Bonne codage :-)

141
MadBoy

Bien que vous puissiez stocker des fichiers de cette façon, le logiciel présente des inconvénients importants:

  • La plupart des bases de données ne sont pas optimisées pour des quantités gigantesques de données binaires, et les performances des requêtes se dégradent souvent considérablement à mesure que la table se gonfle, même avec des index. (SQL Server 2008, avec le type de colonne FILESTREAM, constitue l'exception à la règle.)
  • La sauvegarde/réplication de la base de données devient extrêmement lente.
  • Il est beaucoup plus facile de gérer un lecteur corrompu avec 2 millions d'images (il suffit de remplacer le disque sur le RAID) qu'une table de base de données corrompue.
  • Si vous supprimez accidentellement une douzaine d’images sur un système de fichiers, votre équipe d’exploitation peut les remplacer assez facilement à partir d’une sauvegarde et, étant donné que l’indice de table est très petit, il peut être restauré rapidement. Si vous supprimez accidentellement une douzaine d'images dans une table de base de données géante, l'attente sera longue et pénible pour restaurer la base de données à partir d'une sauvegarde, ce qui paralysera tout votre système.

Ce ne sont là que quelques-uns des inconvénients que je peux avoir par cœur. Pour les petits projets, il peut être intéressant de stocker les fichiers de cette manière, mais si vous concevez un logiciel de niveau entreprise, je le déconseille vivement.

9
Dan Story

Cela dépend vraiment du serveur de base de données.

Par exemple, SQL Server 2008 prend en charge un type de données FILESTREAM pour exactement cette situation.

Sinon, si vous utilisez un MemoryStream, il a une méthode ToArray() qui convertira en un byte[] - ceci peut être utilisé pour remplir un champ varbinary ..

7
Oded

En confirmant que je pouvais utiliser la réponse publiée par MadBoy et modifiée par Otiel à la fois sur MS SQL Server 2012 et 2014, en plus des versions précédemment répertoriées à l'aide de colonnes varbinary (MAX).

Si vous vous demandez pourquoi vous ne pouvez pas utiliser "Filestream" (indiqué dans une réponse distincte) en tant que type de données dans le concepteur de tables SQL Server ou pourquoi vous ne pouvez pas définir le type de données d'une colonne sur "Filestream" à l'aide de T-SQL, c'est parce que FILESTREAM est un stockage. attribut du type de données varbinary (MAX). Ce n'est pas un type de données en soi.

Consultez ces articles sur la configuration et l'activation de FILESTREAM sur une base de données: 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

Une fois configuré, une colonne varbinary (max) activée pour filestream peut être ajoutée comme suit:

ALTER TABLE TableName

ADD ColumnName varbinary (max) FILESTREAM NULL

ALLER

2
Brian C

Je vais décrire la manière dont j'ai stocké les fichiers, dans SQL Server et Oracle. Cela dépend en grande partie de la manière dont vous obtenez le fichier, en premier lieu, quant à la manière dont vous obtiendrez son contenu, et de la base de données que vous utilisez pour le contenu dans lequel vous le stockerez. Ce sont 2 exemples de base de données distincts avec 2 méthodes distinctes pour obtenir le fichier que j'ai utilisé.

SQL Server

Réponse courte: J'ai utilisé une chaîne d'octets en base64 que j'ai convertie en un fichier byte[] Et que j'ai stockée dans un champ varbinary(max).

Longue réponse:

Supposons que vous importez via un site Web, vous utilisez donc un contrôle <input id="myFileControl" type="file" /> Ou React DropZone. Pour obtenir le fichier, vous utilisez une fonction telle que var myFile = document.getElementById("myFileControl")[0]; ou myFile = this.state.files[0];.

À partir de là, j'obtiendrais la chaîne base64 en utilisant le code ici: Convertir entrée = fichier en tableau d'octets (utiliser la fonction UploadFile2).

Ensuite, j'obtiendrais cette chaîne, le nom du fichier (myFile.name) Et taperais (myFile.type) Dans un objet JSON:

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

et postez le fichier sur un serveur MVC à l'aide de XMLHttpRequest, en spécifiant un type de contenu de application/json: xhr.send(JSON.stringify(myJSONObj);. Vous devez construire un ViewModel pour le lier avec:

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

et spécifiez [FromBody]MyModel myModelObj comme paramètre passé en:

[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)

Ensuite, vous pouvez ajouter ceci à cette fonction et l'enregistrer en utilisant Entity Framework:

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); étant la ligne opératoire.

Vous auriez besoin d'un modèle pour représenter la table de base de données:

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; }
}

Cela enregistrera les données dans un champ varbinary(max) en tant que byte[]. Name et Type étaient nvarchar(250) et nvarchar(10), respectivement. Vous pouvez inclure la taille en l'ajoutant à votre table en tant que int colonne & MY_ATTACHMENT_TABLE_MODEL En tant que public int Size { get; set;}, Puis en ajoutant la ligne tblAtchm.Size = System.Convert.FromBase64String(myModelObj.file).Length; ci-dessus.

Oracle

Réponse courte: convertissez-le en byte[], Attribuez-le à un OracleParameter, ajoutez-le à votre OracleCommand et mettez à jour le champ BLOB de votre table à l'aide d'une référence à la valeur ParameterName du paramètre: :BlobParameter

Réponse longue: Quand j'ai fait cela pour Oracle, j'utilisais un OpenFileDialog et j'ai récupéré et envoyé les informations de fichier/octets de cette façon:

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);
}

Et dans la mise à jour Oracle, réalisée avec ADO:

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
           );
    }
}

Et le type de données pour la colonne FILE_CONTENTS Était BLOB, le FILE_SIZE Était NUMBER(10,0), LAST_MODIFIED Était DATE, et le reste était NVARCHAR2(250).

2
vapcguy

Oui, le meilleur moyen de stocker un fichier dans une base de données consiste généralement à enregistrer le tableau d'octets dans une colonne BLOB. Vous voudrez probablement que quelques colonnes stockent en plus les métadonnées du fichier, telles que le nom, l'extension, etc.

Ce n'est pas toujours une bonne idée de stocker des fichiers dans la base de données. Par exemple, la taille de la base de données augmentera rapidement si vous y stockez des fichiers. Mais tout dépend de votre scénario d'utilisation.

2
driis

Quelle base de données utilisez-vous? Normalement, vous ne sauvegardez pas les fichiers dans une base de données, mais je pense que SQL 2008 est supporté par celle-ci ...

Un fichier est une donnée binaire, donc UTF 8 n’a aucune importance ici.

UTF 8 est important lorsque vous essayez de convertir une chaîne en un tableau d'octets ... pas un fichier en tableau d'octets.

1
Peter