wake-up-neo.net

¿Guardar cualquier archivo en la base de datos, simplemente convertirlo a una matriz de bytes?

¿Es la conversión de un archivo a una matriz de bytes la mejor manera de guardar CUALQUIER formato de archivo en el disco o en la base de datos var columna binaria?

Entonces, si alguien quiere guardar un archivo .gif o .doc/.docx o .pdf, ¿puedo convertirlo en un bytearray UFT8 y guardarlo en la base de datos como un flujo de bytes?

62
Blankman

Ya que no se menciona a qué base de datos quiere decir que estoy asumiendo SQL Server. A continuación la solución funciona tanto para 2005 como para 2008.

Tienes que crear una tabla con VARBINARY(MAX) como una de las columnas. En mi ejemplo, he creado la tabla Raporty con la columna RaportPlik siendo VARBINARY(MAX) columna.

Método para poner file en la base de datos 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();
    }
}

Este método es obtener file de la base de datos y guardarlo en 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);
            }
    }
}

Este método es obtener file de la base de datos y ponerlo como 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;
}

Este método es poner MemoryStream en la base de datos:

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

Codificación feliz :-)

137
MadBoy

Si bien puede almacenar archivos de esta manera, tiene compensaciones significativas:

  • La mayoría de los DB no están optimizados para cantidades gigantescas de datos binarios, y el rendimiento de las consultas a menudo se degrada dramáticamente a medida que la tabla aumenta, incluso con índices. (SQL Server 2008, con el tipo de columna FILESTREAM, es la excepción a la regla).
  • La copia de seguridad/replicación de la base de datos se vuelve extremadamente lenta.
  • Es mucho más fácil manejar una unidad dañada con 2 millones de imágenes, simplemente reemplace el disco en el RAID, que una tabla de base de datos que se dañe.
  • Si borra accidentalmente una docena de imágenes en un sistema de archivos, sus empleados de operaciones pueden reemplazarlos con bastante facilidad desde una copia de seguridad, y como el índice de la tabla es muy pequeño en comparación, se puede restaurar rápidamente. Si borra accidentalmente una docena de imágenes en una tabla de base de datos gigante, tiene una larga y dolorosa espera para restaurar la base de datos desde la copia de seguridad, paralizando todo su sistema mientras tanto.

Estos son solo algunos de los inconvenientes que se me ocurren en la cabeza. Para proyectos pequeños, puede valer la pena almacenar archivos de esta manera, pero si está diseñando software de nivel empresarial, le recomiendo encarecidamente que no lo haga.

8
Dan Story

Realmente depende del servidor de base de datos.

Por ejemplo, SQL Server 2008 admite un tipo de datos FILESTREAM para exactamente esta situación.

Aparte de eso, si usa una MemoryStream, tiene un método ToArray() que se convertirá en un byte[] - esto se puede usar para llenar un campo varbinary ..

7
Oded

Confirmando que pude usar la respuesta publicada por MadBoy y editada por Otiel en MS SQL Server 2012 y 2014, además de las versiones enumeradas anteriormente usando las columnas varbinary (MAX).

Si se pregunta por qué no puede "Filestream" (anotado en una respuesta por separado) como un tipo de datos en el diseñador de tablas de SQL Server o por qué no puede configurar el tipo de datos de una columna en "Filestream" usando T-SQL, es porque FILESTREAM es un almacenamiento atributo del tipo de datos varbinary (MAX). No es un tipo de datos por sí mismo.

Consulte estos artículos sobre cómo configurar y habilitar FILESTREAM en una base de datos: 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

Una vez configurada, una columna varbinary (max) habilitada para filestream se puede agregar así:

ALTER TABLE TableName

ADD ColumnName varbinary (max) FILESTREAM NULL

IR

2
Brian C

Voy a describir la forma en que he almacenado los archivos, en SQL Server y Oracle. Depende en gran medida de cómo obtenga el archivo, en primer lugar, de cómo obtendrá su contenido, y dependerá de qué base de datos esté utilizando para el contenido en el que lo almacenará y cómo lo almacenará. Estos son 2 ejemplos de bases de datos independientes con 2 métodos separados para obtener el archivo que usé.

SQL Server

Respuesta corta: utilicé una cadena de bytes base64 que convertí en un byte[] y almacené en un campo varbinary(max).

Respuesta larga:

Digamos que estás subiendo a través de un sitio web, así que estás usando un control <input id="myFileControl" type="file" />, o React DropZone. Para obtener el archivo, estás haciendo algo como var myFile = document.getElementById("myFileControl")[0]; o myFile = this.state.files[0];.

A partir de ahí, obtendría la cadena base64 utilizando el código aquí: Convertir input = archivo en una matriz de bytes (usar la función UploadFile2).

Luego obtendría esa cadena, el nombre del archivo (myFile.name) y escribo (myFile.type) en un objeto JSON:

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

y publique el archivo en un servidor MVC backend usando XMLHttpRequest, especificando un Tipo de Contenido de application/json: xhr.send(JSON.stringify(myJSONObj);. Tienes que construir un ViewModel para enlazarlo con:

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

y especifique [FromBody]MyModel myModelObj como el parámetro pasado 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)

Luego, puede agregar esto a esa función y guardarlo usando 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); siendo la línea operativa.

Necesitarías un modelo para representar la tabla de la base de datos:

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

Esto guardará los datos en un campo varbinary(max) como un byte[]. Name y Type eran nvarchar(250) y nvarchar(10), respectivamente. Puede incluir el tamaño agregándolo a su tabla como int columna & MY_ATTACHMENT_TABLE_MODEL como public int Size { get; set;}, y agregue la línea tblAtchm.Size = System.Convert.FromBase64String(myModelObj.file).Length; anterior.

Oracle

Respuesta corta: conviértala en un byte[], asigne un OracleParameter, agréguela a su OracleCommand, y actualice el campo BLOB de su tabla usando una referencia al valor ParameterName del parámetro: :BlobParameter

Respuesta larga: cuando hice esto para Oracle, estaba usando una OpenFileDialog y recuperé y envié la información de bytes/archivos de esta manera:

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

Y dentro de la actualización de Oracle, hecho con 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
           );
    }
}

Y el tipo de datos para la columna FILE_CONTENTS era BLOB, el FILE_SIZE era NUMBER(10,0), LAST_MODIFIED era DATE, y el resto era NVARCHAR2(250).

2
vapcguy

Sí, generalmente la mejor manera de almacenar un archivo en una base de datos es guardar la matriz de bytes en una columna BLOB. Es probable que desee un par de columnas para almacenar adicionalmente los metadatos del archivo, como el nombre, la extensión, etc.

No siempre es una buena idea almacenar archivos en la base de datos; por ejemplo, el tamaño de la base de datos aumentará rápidamente si almacena archivos en ella. Pero todo depende de su escenario de uso.

2
driis

¿Qué base de datos estás utilizando? normalmente no guardas archivos en una base de datos, pero creo que SQL 2008 tiene soporte para eso ...

Un archivo es datos binarios, por lo tanto, UTF 8 no importa aquí.

UTF 8 importa cuando intentas convertir una cadena a una matriz de bytes ... no un archivo a una matriz de bytes.

1
Peter