2010-12-09 76 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; 
} 
相关问题