2010-12-09 73 views
9

這是場景:如何使用SqlDataReader在普通的舊C#對象中使用BLOB提供的流?

  • 我們存儲文件,例如,相對較大的文檔(10-300MB),在我們的MSSQL數據庫的斑點中。
  • 我們有一個非常小的域模型,因此我們使用乾淨的SqlDataReader方法來存儲我們的存儲庫,而不是ORM,以避免不必要的依賴關係。
  • 我們希望在ASP.NET/ASP.NET MVC網頁的服務器上下文中使用對象。
  • 我們不希望的斑臨時存儲字節[],以避免服務器

所以,我一直在做的就是實現自己的SqlBlobReader高內存使用情況。它繼承了Stream和IDisposable,並且在實例化過程中,我們必須提供一個SqlCommand,其中包含一個返回一行的查詢,當然這是我們想要流的blob。然後我的C#域對象可以有一個Stream類型的屬性,它返回一個SqlBlobReader實現。當流式傳輸到ASP.net MVC中的FileContentStream等時,可以使用此流。

它會立即執行帶SequentialAccess的ExecuteReader以啓用從MSSQL服務器流式傳輸blob。這意味着在使用它時我們必須小心地儘快處理流,並且當需要時我們總是懶惰地實例化SqlBlobReader。在我們的域對象中使用庫調用。

我的問題則是:

  • 這是使用SqlDataReader的,而不是一個ORM時對普通的舊域對象實現斑點流的一個聰明的辦法?
  • 我不是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 =平原老CLR對象。不是普通的老C#對象:) – 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的修復之外,我修改了構造函數以打開連接並處理,以防打開或執行失敗以減少調用者中的代碼/異常處理。 (不知道Dispose可以從構造函數中調用)。構造函數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; 
} 
相關問題