2010-12-09 2 views
9

Это сценарий:Как сделать потоки из BLOB доступны в простых старых объектах C# при использовании SqlDataReader?

  • Мы храним файлы, например. относительно большие документы (10-300 МБ), в блоках в нашей базе данных MSSQL.
  • У нас очень маленькая модель домена, поэтому мы используем чистый метод SqlDataReader для нашего репозитория, а не ORM, чтобы избежать ненужных зависимостей.
  • Мы хотим использовать объекты в контексте сервера на веб-страницах ASP.NET/ASP.NET MVC.
  • Мы не хотим, чтобы временно хранить сгустки в байт [], чтобы избежать использования памяти на сервере

Так что я делал это, чтобы реализовать свой собственный SqlBlobReader. Он наследует Stream и IDisposable, и во время создания экземпляра мы должны предоставить SqlCommand, содержащий запрос, который возвращает одну строку с одним столбцом, который является блобом, который мы хотим передать, конечно. Тогда мои объекты домена C# могут иметь свойство типа Stream, которое возвращает реализацию SqlBlobReader. Этот поток можно затем использовать при потоковой передаче в FileContentStream в ASP.net MVC и т. Д.

Он немедленно сделает ExecuteReader с SequentialAccess, чтобы включить потоковое воспроизведение blob с сервера MSSQL. Это означает, что мы должны быть осторожны, чтобы избавиться от потока ASAP при его использовании и что мы всегда лениво инстанцируем SqlBlobReader, когда это необходимо, например. используя вызов репозитория внутри наших объектов домена.

Мой вопрос тогда:

  • Это умный способ достижения потоков сгустков на простых старых доменных объектов при использовании SqlDataReader вместо ОРМ?
  • Я не эксперт ADO.NET, делает ли это внедрение разумным?

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

    } 

} 

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

В DocumentFile.cs:

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

В 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

Для справки: POCO = Plain Old CLR Object. Not Plain Old C# Object :) – 2010-12-09 09:55:52

+0

Да, я просто очень C#, пока не получаю F # под моим поясом. Да, да. – 2010-12-09 10:09:12

ответ

7

Есть ошибка; Вы игнорируете арг пользователя, и вы, вероятно, следует охранять для -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); 
    } 

, вероятно, следует:

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

(иначе вы пишете в ту часть буфера)

Но, как правило, хорошо выглядит.

0

Это великолепно! Спасибо за эту память. Помимо исправления Marc, я изменил конструктор, чтобы открыть соединение, и удалю в случае, если open или execute не позволяет уменьшить обработку кода/исключений в вызывающем. (Не знал, что Dispose может быть вызван из конструктора).Конструктор мод:

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