El experto de Axpe Consulting: Guardar ficheros binarios en SQL Server

SQL ServerContinuando con la vocación que queremos darle a este blog de Axpe Consulting  os volvemos a presentar un artículo escrito por uno de nuestros expertos en .NET  y Bases de Datos sobre un aspecto técnico que creemos que puede parecer no sólo muy interesante sino que útil a todos vosotros: Cómo guardar ficheros binarios en SQL Server.

En un antiguo proyecto, una aplicación ASP .NET, el cliente expresó el deseo de guardar en la base de datos sus cartas, basadas en plantillas Word y generadas mediante la aplicación. Querían guardarlas en la base de datos porque, al ser documentos oficiales, tenían que poder volver a imprimirse o visualizarse exactamente igual a como se crearon en su día. Por lo tanto, necesitamos un método para guardar los ficheros Word en la base de datos en formato binario, como stream de datos y ser capaces de poder recuperarlos después para su visualización.

Nota: no es el objetivo de este post la creación de ficheros Word desde código C#. Podéis usar para ello la automatización Word, o componentes de terceros como los publicados por Aspose, que son excelentes.

Vamos a crear una tabla simple en SQL Server para alojar los documentos. Para este ejemplo, la llamaremos DocsBinarios, y tendrá la siguiente estructura:

Campo Tipo Nulos?
DocId Int (identity) No
Documento Image No
NombreDoc VarChar(100) No

Es bastante autoexplicativo: el campo DocId es un campo de clave primaria autogenerado. El campo Documento es el que va a almacenar los streams de bits, es decir los propios ficheros Word en formato binario. El campo NombreDoc almacenará el nombre que se proporcionó originalmente al documento cuando se generó.

Después crearemos un procedimiento almacenado, UploadDocs, que servirá para guardar registros en esta tabla:

CREATE PROCEDURE UploadDoc(@doc AS Image, @nombre AS VarChar(100)) 
AS
INSERT INTO 
DocsBinarios (Documento, NombreDoc) values (@doc, @nombre)
GO

También es bastante autoexplicativo: un simple INSERT INTO que recibe como parámetros un Image y un VarChar

Y éste es el método que guarda los datos en la base de datos:

private void GuardarFicheroBDD(string sRuta, string sFichero)
{
    
//Creamos un nuevo objeto de tipo FileStream para leer el fichero
    //Word en modo binario
    
System.IO.FileStream  fs = new FileStream(sRuta + sFichero,
        System.IO.FileMode.Open)
;
    
//Creamos un array de bytes para almacenar los datos leídos por fs.
    
Byte[] data = new byte[fs.Length];
    
//Y guardamos los datos en el array data
    
fs.Read(data, 0, Convert.ToInt32(fs.Length));
    
//Abrimos una conexion. En este caso los datos de la cadena de
    //conexion a la base de datos se recuperan de una sección del
    //fichero web.config mediante ConfigurationSettings
    
SqlConnection cnn =
       new 
SqlConnection(ConfigurationSettings.AppSettings[“conexionBD”]);
    
cnn.Open();
    
//Creamos un comando de tipo StoredProcedure para invocar a
    //UploadDocs
    
SqlCommand cmd = new SqlCommand(“UploadDoc”, cnn);
    
cmd.CommandType CommandType.StoredProcedure;
   
//Añadimos los parametros esperados y los valores de los mismos
    
cmd.Parameters.Add(“@doc”, data)//los datos del fichero Word
    
cmd.Parameters.Add(“@nombre”, sFichero)//y su nombre
    //Ejecutamos el procedimiento almacenado, que inserta un nuevo
    //registro en DocsBinarios con los datos que queremos introducir
    
cmd.ExecuteNonQuery();
    
//Cerramos la conexión y el fichero 
    
cnn.Close();
    
fs.Close();
}

Ahora veamos el método para recuperar esos datos de la base de datos y mostrarlos como un fichero Word:

private void LeerDeBD()
{
    
//Abrimos la conexion, exactamente igual que antes
    
SqlConnection cnn =
        new 
SqlConnection(ConfigurationSettings.AppSettings[“conexionBD”]);
    
cnn.Open();
    
//Este es el comando que abre el registro que deseamos. Para este
    //ejemplo abrimos siempre el primer registro, habría que modificar
    // este código para que el método recibiera como parámetro el
    //registro que queremos abrir, claro.
    
SqlCommand comm = new SqlCommand(“SELECT * FROM DocsBinarios ” +
        ” WHERE docId = 1″
, cnn);
    
comm.CommandType CommandType.Text;
    
SqlDataAdapter da = new SqlDataAdapter(comm);
    
DataSet ds = new DataSet(“Binarios”);
    
da.Fill(ds);
    
//Creamos un array de bytes que contiene los bytes almacenados
    //en el campo Documento de la tabla
    
byte[] bits ((byte[])(ds.Tables[0].Rows[0].ItemArray[1]));            
    
cnn.Close();
    
//Vamos a guardar ese array de bytes como un fichero en el
    //disco duro, un fichero temporal que después se podrá descartar.
    //Para evitar problemas de concurrencia de usuarios,
    //generamos un nombre único para el mismo
    
string sFile “tmp” + GenerarNombreFichero() + “.doc”;
    
//Creamos un nuevo FileStream, que esta vez servirá para
    //crear un fichero con el nombre especificado
    
FileStream fs = new FileStream(Server.MapPath(“.”) +
        @”\DocsGenerados\” 
+ sFile, FileMode.Create);
    
//Y escribimos en disco el array de bytes que conforman
    //el fichero Word
    
fs.Write(bits, 0, Convert.ToInt32(bits.Length));
    
fs.Close();
    
//Para mostrar el fichero, utilizamos una función
    //JavaScript llamada mostrarFichero (que lo único que
    //hace es cargar el fichero especificado)
    //y hacemos que se ejecute en un pop-up.
    
string script “<script languaje=’javascript’> “;
    
script +“mostrarFichero(‘DocsGenerados/” + sFile + “‘) “;
    
script +“</script>” + Environment.NewLine;
    
Page.RegisterStartupScript(“mostrarFichero”,script);
}
Y listo. Mediante éste último método el fichero Word se carga desde la base de datos y se muestra en un popup y en todo su esplendor.

Para que tengáis el código completo, ésta es la función JavaScript que carga el fichero generado desde base de datos:

<script language=“javascript”>
function mostrarFichero(destino) {
window.open(destino,null,“directories=no,height=600,
    width=800,left=0,top=0,location=no,menubar=yes,
    status=no,toolbar=yes,resizable=yes”
)
document.forms(0).submit();
}
</script>

Y éste el método GenerarNombreFichero(), que utiliza la cuenta de ticks del servidor para crear nombres cuasi únicos de fichero.

private string GenerarNombreFichero()
{
    
int ultimoTick 0;
    while
(ultimoTick==Environment.TickCount) 
    {
        System.Threading.Thread.Sleep(
1);
    
}
    ultimoTick
=Environment.TickCount;
    return 
DateTime.Now.ToString(“yyyyMMddhhmmss”) + “.” +
        ultimoTick.ToString()
;
}

¡Comparte!Email this to someoneTweet about this on TwitterShare on FacebookShare on Google+Pin on PinterestShare on LinkedInShare on Tumblr

6 Comments

  1. Por favor, descolgad este artículo porque lo único que hace es daros mala imagen, es una solución chapucera sobre todo cuando existe un tipo de datos en SQLServer destinado a tal fin y que ademas permite buscar dentro del contenido…. En fin, si este es vuestro nivel de expertise me siento aliviado de continuar con mis actuales proveedores.

  2. Como autor del artículo, te agradezco tus amables palabras al respecto del nivel del mismo. Como debes saber, existen muchas soluciones a un mismo problema, y la mayoría de los problemas tienen más de una solución válida. En mi artículo jamás menciono que la mía sea la “única” solución existente, es una de tantas. Si tú tienes una solución mejor, cosa que no dudo, te agradecería que nos la hicieras saber mediante un comentario, o un link a tu blog, o al blog de cualquiera de tus excelsos proveedores; o de cualquier otra forma para que entre todos consigamos el objetivo que, a estas alturas, creo que debería ser obvio: aprender.

    Carlos Manuel Pérez

  3. A mí también me parece un artículo interesante, ya que estoy en un proyecto en el cual tendré que almacenar archivos de office (word, excel, power point, etc.) en una base de datos SQL Server, y me viene como quien dice, como “anillo al dedo”.
    Carlos ¿podrías dar el mismo ejemplo pero en Visual Basic .NET y en Delphi, si es que manejas Delphi?
    Muchas gracias.

    Nelson Rivas
    Asunción – Paraguay

  4. hola que tal, si tienes el codigo en .net, te lo agradeceria que me facilites ya que tengo que hacer un proyecto en el cual tengo que guardar archivos de office a la base de datos

    gracias

    Stalin

  5. Hola a todos, bueno este articulo esta muy interesante a mi me funciona pero cuado se abre la ventana popup tambien se abre el archivo de word, pero se cierra rapidamente la ventana popup hay alguna forma de colocar una pagina que funcione como contenedir de word para que a travez de ella se pueda ver los documentos word pero que los usuarios no tengan acceso al documento doc., Gracias.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *