Lluís Franco on Geeks.ms
  • Home

Editar documentos almacenados como array de bits en SQL Server [FileStream] (3/n)

  • By lfranco
  • Ago-19-2011
  • Sin categoría
  • 3 Comments.

Es viernes, así que intentaré terminar la serie. Espero que no me quede un post muy ‘tocho’ 😛

Después de abrir boca con los dos posts anteriores, en los que hemos mostrado cómo crear una tabla con almacenamiento FILESTREAM, y posrteriormente cómo almacenar en ella documentos en forma de información binaria, hoy vamos a terminar la serie viendo cómo poder visualizar esta información mediante su aplicación asociada (Word, Excel, Acrobar Reader, etc.) y cómo no, editarla para guardar los cambios otra vez en la base de datos.

fsword

Mis disculpas 🙂

Antes de continuar os quiero decir que mi intención original era encontrar una solución más elegante, pero como no he sido capaz de encontrarla (y por lo que he visto en Internet, no he sido el único) os dejo una solución que no es tan elegante, pero al menos funciona.

Que cual era mi intención original? Pues me hubiese gustado poder abrir directamente la información binaria en -por ejemplo- Word. Creía que lo podría conseguir obteniendo un handle y pasándoselo a la aplicación asociada… pero los handles obtenidos mediante OpenSqlFilestream son locales al proceso 🙁

Dicho de otro modo, podemos acceder al fichero, leerlo, editarlo, pero sólo si lo hace nuestra aplicación. Si el fichero debe ser controlado a través de otra aplicación no podemos pasarle el handle. Así que esta opción no sirve.

Otra opción que contemplé (inspirado en mi querido y odiado SharePoint) fue crear un manejador HTTP con ASP.NET, ya que algunas aplicaciones son capaces de trabajar con documentos abiertos a través de una URL. Sin embargo, sólo conseguí hacerlo funcionar con documentos de Office (y no en el 100% de los casos).

La solución. Mi solución (seguro que hay más)

Así pues tuve que recurrir a la opción que pretendía evitar a toda costa, que no es otra que: Leer la información binaria, crear un fichero temporal en la estación cliente y abrirlo con la aplicación asociada. Que tiene de malo esta solución? Pues que para visualizar documentos funciona muy bien, pero en realidad estamos mostrando –y editando- una copia del documento, de modo que para revertir los cambios que hace el usuario a la base de datos tenemos un problema. Un problemón.

Después de darle algunas vueltas y comentarlo con más gente (gracias Pablo Gavela!) al final me decanté por utilizar la clase Process. Si, la misma clase process que hemos usado miles de veces para ejecutar aplicaciones o mostrar documentos. La clase process puede lanzar un proceso de forma síncrona o asíncrona.

La primera la deseché en seguida porque creo que usa algo que los viejos programadores del API de Win32 aprendimos a temer: WaitForExit (que llama a WaitForSingleObject). Además, la aplicación deja de responder hasta que se termina el proceso lanzado, con lo que ni siquiera repinta la ventana.

La segunda permite lanzar un proceso y monitorizar el momento en que se cierra mediante el evento ‘Exited’. De modo que podemos saber el momento en que se cierra la aplicación asociada, verificar si se ha cambiado el documento y en caso afirmativo volver a guardarlo en la base de datos.

Nota importante: Si queremos que se dispare el evento ‘Exited’ hay que activar la propiedad ‘EnableRaisingEvents’.

Un poco rocambolesco? Tal vez. Así que si alguien encuentra una solución más sencilla que lo haga público. Por favor 🙂

Al fin! El maldito código

Mi propuesta es crear una clase derivada de Process con la información adicional que necesitamos para el manejo de los datos binarios. En este caso particular necesito que el proceso ‘conozca’ el identificador del registro (FileId Guid), el nombre original del documento (o al menos su extensión), y evidentemente los propios datos binarios.

La idea es pasarle estos datos ‘extra’ en el constructor, y posteriormente llamar a un método que se encargue de crear el fichero temporal y obtener la fecha de la última escritura. Posteriormente ‘escucharemos’ el evento ‘Exited’ y cuando se dispare verificaremos si hay cambios (comparando la fecha de última escritura con la anterior) y si los hay, guardaremos los cambios en la base de datos a través del FileId que hemos pasado enteriormente al proceso.

La clase ProcessController

public class ProcessController : Process

{

    DateTime originalLastWriteTime;

    Byte[] document = null;

 

    string tempfileName = string.Empty;

    public string TempFileName

    {

        get

        {

            return tempfileName;

        }

    }

 

    Guid fileId;        

    public Guid FileId

    {

        get

        {

            return fileId;

        }

    }

 

    public ProcessController(Byte[] documentBytes, Guid docId, string originalfilename)

        : base()

    {

        tempfileName = Path.GetTempFileName().Replace("tmp", getExtension(originalfilename));

        document = documentBytes;

        fileId = docId;

        this.StartInfo.FileName = tempfileName;

    }

 

    public void EditInAssociatedApplication()

    {

        if (tempfileName == null) return;

        if (File.Exists(tempfileName)) File.Delete(tempfileName);

        File.WriteAllBytes(tempfileName, document);

        originalLastWriteTime = File.GetLastWriteTime(tempfileName);

        this.EnableRaisingEvents = true;

        this.Start();

    }

 

    public bool HasChanged()

    {

        if (tempfileName == null) return false;

        var modifiedtime = File.GetLastWriteTime(tempfileName);

        return (modifiedtime != originalLastWriteTime);

    }

 

    private string getExtension(string filename)

    {

        var parts = filename.Split('.');

        if (parts.Length > 0)

            return parts[parts.Length - 1];

        else

            return string.Empty;

    }    

}

Para usar esta clase en nuestra aplicación lo mejor es tener una lista con los procesos que vamos abriendo, y por cada uno de ellos escuchar su evento ‘Exited’. De este modo cada vez que abrimos un proceso nuevo lo agregamos a la lista, y cada vez que se cierra un proceso, comprobamos si hay cambios y lo volcamos a la base de datos. Un ejemplo:

List<ProcessController> Processes = new List<ProcessController>();

 

private void editDocument(Guid docId)

{

    var filestreamDoc = context.FileStreamDocuments.FirstOrDefault(

        p => p.FileId == docId);

    if (filestreamDoc == null) return;

    ProcessController process = new ProcessController(

        filestreamDoc.Document.ToArray(),

        filestreamDoc.FileId, filestreamDoc.OriginalPath);

    process.Exited += process_Exited;

    process.EditInAssociatedApplication();

    Processes.Add(process);

}

 

void process_Exited(object sender, EventArgs e)

{

    ProcessController process = sender as ProcessController;

    if (process == null) return;

    if (process.HasChanged())

    {

        saveDocument(process.FileId, process.TempFileName);

    }

}

 

private void saveDocument(Guid docId, string filename)

{

    var filestreamDoc = context.FileStreamDocuments.FirstOrDefault(

        p => p.FileId == docId);

    if (filestreamDoc == null) return;

    if (File.Exists(filename))

    {

        filestreamDoc.Document = File.ReadAllBytes(filename);

        context.SubmitChanges();

    }

}

No quiero alargar más el post, así que os dejo algo como ejercicio por si alguien se anima:

  • Cada vez que se cierra un proceso deberíamos eliminarlo de la lista.
  • Si el usuario cierra la aplicación y existen procesos abiertos, deberíamos avisarlo.

Ala, ya doy por acabada la serie. Y justo a tiempo. Nos leemos 🙂

Comments

3 Responsesso far

  1. supercordobes dice:
    19 agosto, 2011 a las 4:31 pm

    Muy buena la serie, relacionado con este post: http://geeks.ms/blogs/jirigoyen/archive/2010/03/25/constr-250-yete-tu-propio-gestor-documental.aspx

    Otra forma de saber si un archivo se modifico es mediante la utilización del hash (obtener el hash antes entregar el doc al usuario y luego generarlo de nuevo cuando se va a guardar y comparar)

    Salu2

    Responder
  2. lfranco dice:
    19 agosto, 2011 a las 5:00 pm

    Me alegro que te haya gustado!
    Gracias por el link 😉

    Responder
  3. jirigoyen dice:
    19 agosto, 2011 a las 9:40 pm

    Excelente serié, creo además que es mucho mas adecuado para un gestor documental utilizar tu solución con Filestream pues si los archivos alojados son muy grandes la base de datos puede llegar a ser poco manejable.

    El problema de que al editar se tenga que grabar el archivo de nuevo a disco, nosotros hemos tenido algún problema con los de autocad, algunos de mas de 20 gigas que tardaban bastante, hemos tenido que realizar llamadas asíncronas para no bloquear el programa, una de las ventajas es que fácilmente puedes implementar un control de versiones.

    Seguramente el rendimiento sea menor en la búsqueda de contenidos indizados, aunque habría que probarlo.

    Otra solución para aquellos que tengan Sharepoint es guardar y acceder a los documentos desde la aplicación utilizando un ws que ataque a Sharepoint, nosotros lo implementamos hace varios años y la verdad es que funciono muy bien.

    Un saludo.

    Responder

Deja un comentario Cancelar respuesta

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

← Previous Post Next Post →

Tags

async Back best practices

Entradas recientes

  • Video de mi charla en la #dotNetSpain2016
  • I’m back. Miss me?
  • Office365 actualizado a 2013 para nuevas suscripciones
  • Serializar listas genéricas en aplicaciones WinRT
  • [TPL] Problemas de concurrencia

Comentarios recientes

  • Darling Chavez en Tip: Mostrar objetos relacionados en DevExpress GridControl
  • Alexander en [TPL] Problemas de concurrencia
  • cristinakity en Funciones escalares en TSQL, JOINS, CROSS APPLY, y la madre que parió al topo.
  • cristinakity en Funciones escalares en TSQL, JOINS, CROSS APPLY, y la madre que parió al topo.
  • anonymous en HowTo: Crear una pantalla de inicio (splash screen)

Archivos

  • marzo 2016
  • marzo 2013
  • octubre 2012
  • septiembre 2012
  • agosto 2012
  • febrero 2012
  • diciembre 2011
  • noviembre 2011
  • octubre 2011
  • septiembre 2011
  • agosto 2011
  • junio 2011
  • mayo 2011
  • abril 2011
  • febrero 2011
  • enero 2011
  • diciembre 2010
  • noviembre 2010
  • octubre 2010
  • agosto 2010
  • julio 2010
  • marzo 2010
  • febrero 2010
  • enero 2010
  • diciembre 2009
  • noviembre 2009
  • octubre 2009
  • septiembre 2009
  • agosto 2009
  • julio 2009
  • junio 2009
  • mayo 2009
  • abril 2009
  • marzo 2009
  • febrero 2009
  • enero 2009
  • diciembre 2008
  • noviembre 2008
  • octubre 2008
  • septiembre 2008
  • agosto 2008
  • julio 2008
  • junio 2008
  • mayo 2008
  • abril 2008
  • marzo 2008
  • febrero 2008
  • enero 2008
  • diciembre 2007
  • noviembre 2007
  • octubre 2007
  • septiembre 2007
  • agosto 2007
  • abril 2007
  • febrero 2007
  • enero 2007

Categorías

  • .NET
  • C#
  • Channel9
  • Evento
  • Personal
  • Videos

Meta

  • Acceder
  • RSS de las entradas
  • RSS de los comentarios
  • WordPress.org
About This Site

A cras tincidunt, ut tellus et. Gravida scel ipsum sed iaculis, nunc non nam. Placerat sed phase llus, purus purus elit.

Archives Widget
  • January 2010
  • December 2009
  • November 2009
  • October 2009
Categories
  • Entertainment
  • Technology
  • Sports & Recreation
  • Jobs & Lifestyle
Search
  • facebook
  • twitter
  • rss

Powered by WordPress  |  Business Directory by InkThemes.

This site uses cookies: Find out more.