2010-12-09 7 views
9

Este es el escenario:¿Cómo hacer que las secuencias de BLOB estén disponibles en objetos viejos sin formato C# cuando se usa SqlDataReader?

  • Almacenamos archivos, por ejemplo, documentos relativamente grandes (10-300MB), en blobs en nuestra base de datos MSSQL.
  • Tenemos un modelo de dominio muy pequeño, por lo que utilizamos el enfoque limpio SqlDataReader para nuestro repositorio, en lugar de un ORM, para evitar dependencias innecesarias.
  • Queremos utilizar los objetos en el contexto del servidor en las páginas web ASP.NET/ASP.NET MVC.
  • No queremos para almacenar temporalmente las manchas en el byte [], para evitar un alto uso de memoria en el servidor

Así que lo que he estado haciendo es poner en práctica mi propia SqlBlobReader. Hereda Stream y IDisposable y durante la instanciación debemos suministrar un SqlCommand que contiene una consulta que devuelve una fila con una columna, que es la burbuja que queremos transmitir, por supuesto. Entonces mis objetos de dominio C# pueden tener una propiedad de tipo Stream que devuelve una implementación de SqlBlobReader. Esta secuencia se puede usar cuando se transmite a un FileContentStream en ASP.net MVC, etc.

Ejecutará inmediatamente un ExecuteReader con SequentialAccess para permitir la transmisión del blob desde el servidor MSSQL. Esto significa que debemos tener cuidado de disponer de la transmisión lo antes posible cuando la utilicemos, y que siempre instanciamos perezosamente SqlBlobReader cuando es necesario, p. usando una llamada de repositorio dentro de nuestros objetos de dominio.

Mi pregunta es entonces:

  • ¿Es esta una forma inteligente de conseguir corrientes de gotas de objetos de dominio llanura de edad cuando se utiliza SqlDataReader en lugar de un ORM?
  • No soy un experto en ADO.NET, ¿la implementación parece razonable?

SqlBlobReader.cs:

using System; 
using System.Data; 
using System.Data.SqlClient; 
using System.IO; 

namespace Foo 
{ 
    /// <summary> 
    /// There must be a SqlConnection that works inside the SqlCommand. Remember to dispose of the object after usage. 
    /// </summary> 
    public class SqlBlobReader : Stream 
    { 
     private readonly SqlCommand command; 
     private readonly SqlDataReader dataReader; 
     private bool disposed = false; 
     private long currentPosition = 0; 

     /// <summary> 
     /// Constructor 
     /// </summary> 
     /// <param name="command">The supplied <para>sqlCommand</para> must only have one field in select statement, or else the stream won't work. Select just one row, all others will be ignored.</param> 
     public SqlBlobReader(SqlCommand command) 
     { 
     if (command == null) 
      throw new ArgumentNullException("command"); 
     if (command.Connection == null) 
      throw new ArgumentException("The internal Connection cannot be null", "command"); 
     if (command.Connection.State != ConnectionState.Open) 
      throw new ArgumentException("The internal Connection must be opened", "command"); 
     dataReader = command.ExecuteReader(CommandBehavior.SequentialAccess); 
     dataReader.Read(); 
     this.command = command; // only stored for disposal later 
     } 

     /// <summary> 
     /// Not supported 
     /// </summary> 
     public override long Seek(long offset, SeekOrigin origin) 
     { 
     throw new NotSupportedException(); 
     } 

     /// <summary> 
     /// Not supported 
     /// </summary> 
     public override void SetLength(long value) 
     { 
     throw new NotSupportedException(); 
     } 

     public override int Read(byte[] buffer, int index, int count) 
     { 
     long returned = dataReader.GetBytes(0, currentPosition, buffer, 0, buffer.Length); 
     currentPosition += returned; 
     return Convert.ToInt32(returned); 
     } 

     /// <summary> 
     /// Not supported 
     /// </summary> 
     public override void Write(byte[] buffer, int offset, int count) 
     { 
     throw new NotSupportedException(); 
     } 

     public override bool CanRead 
     { 
     get { return true; } 
     } 

     public override bool CanSeek 
     { 
     get { return false; } 
     } 

     public override bool CanWrite 
     { 
     get { return false; } 
     } 

     public override long Length 
     { 
     get { throw new NotSupportedException(); } 
     } 

     public override long Position 
     { 
     get { throw new NotSupportedException(); } 
     set { throw new NotSupportedException(); } 
     } 

     protected override void Dispose(bool disposing) 
     { 
     if (!disposed) 
     { 
      if (disposing) 
      { 
       if (dataReader != null) 
        dataReader.Dispose(); 
       SqlConnection conn = null; 
       if (command != null) 
       { 
        conn = command.Connection; 
        command.Dispose(); 
       } 
       if (conn != null) 
        conn.Dispose(); 
       disposed = true; 
      } 
     } 
     base.Dispose(disposing); 
     } 

     public override void Flush() 
     { 
     throw new NotSupportedException(); 
     } 

    } 

} 

En Repository.cs:

public virtual Stream GetDocumentFileStream(int fileId) 
    { 
    var conn = new SqlConnection {ConnectionString = configuration.ConnectionString}; 
    var cmd = new SqlCommand 
        { 
        CommandText = 
         "select DocumentFile " + 
         "from MyTable " + 
         "where Id = @Id", 
        Connection = conn, 
        }; 


    cmd.Parameters.Add("@Id", SqlDbType.Int).Value = fileId; 
    conn.Open(); 
    return new SqlBlobReader(cmd); 
    } 

En DocumentFile.cs:

public Stream GetStream() 
    { 
    return repository.GetDocumentFileStream(Id); 
    } 

En DocumentController.cs:

// A download controller in ASP.net MVC 2 

    [OutputCache(CacheProfile = "BigFile")] 
    public ActionResult Download(int id) 
    { 
    var document = repository.GetDocument(id); 
    return new FileStreamResult(document.DocumentFile.GetStream(), "application/pdf") 
       { 
        FileDownloadName = "Foo.pdf"; 
       }; 
    } 
+0

Para su información: POCO = Plain Old CLR Object. No es sencillo Objeto C# antiguo :) –

+0

Sí, estoy muy orientado a C# hasta que obtengo F # bajo mi cinturón. Ajá. –

Respuesta

7

Hay un error; están ignorando argumentos del usuario, y es probable que debe protegerse para -ve returned:

public override int Read(byte[] buffer, int index, int count) 
    { 
    long returned = dataReader.GetBytes(0, currentPosition, 
     buffer, 0, buffer.Length); 
    currentPosition += returned; 
    return Convert.ToInt32(returned); 
    } 

probablemente debería ser:

public override int Read(byte[] buffer, int index, int count) 
    { 
    long returned = dataReader.GetBytes(0, currentPosition, 
     buffer, index, count); 
    if(returned > 0) currentPosition += returned; 
    return (int)returned; 
    } 

(de lo contrario se está escribiendo en la parte equivocada de la memoria intermedia)

Pero generalmente se ve bien.

+0

Gracias, veo tu punto. Voy a revisar. Después de mi publicación, me di cuenta de que tal vez la eliminación de la conexión también sea un poco peligrosa, en caso de que varios comandos compartan la misma conexión. Podría controlarse mediante un argumento de constructor, tal vez. No es muy probable en mi escenario, pero puede ser para otros. –

+0

@Geir - hmm; podrías hacer una bandera en el constructor? –

0

¡Qué hermoso! Gracias por este protector de memoria. Además del arreglo de Marc, modifiqué el constructor para abrir la conexión y deshacerme de ella en caso de que el abrir o ejecutar no reduzca el manejo del código/excepción en la persona que llama. (No sabía que Dispose podría llamarse desde el constructor).Constructor mod:

try 
{ 
    this.command = command;  // store for disposal 

    if (command.Connection.State != ConnectionState.Open) 
     command.Connection.Open(); 

    dataReader = command.ExecuteReader(CommandBehavior.SequentialAccess); 
    dataReader.Read();    
} 
catch (Exception ex) 
{ 
    Dispose(); 
    throw; 
} 
Cuestiones relacionadas