在使用SqlDataReader时,如何从普通的旧C#对象中使用BLOB创建流?

这是场景:

  • 我们在MSSQL数据库中的blob中存储文件,例如相对较大的文档(10-300MB)。
  • 我们有一个非常小的域模型,所以我们使用干净的SqlDataReader方法为我们的存储库而不是ORM,以避免不必要的依赖。
  • 我们希望在ASP.NET / ASP.NET MVC网页上使用服务器上下文中的对象。
  • 我们不希望将blob临时存储在byte []中,以避免服务器上的高内存使用量

所以我一直在做的是实现我自己的SqlBlobReader。 它inheritance了Stream和IDisposable,在实例化过程中,我们必须提供一个包含查询的SqlCommand,该查询返回一行包含一列,这当然是我们想要流的blob。 然后我的C#域对象可以具有Stream类型的属性,该属性返回SqlBlobReader实现。 然后,当流式传输到ASP.net MVC等中的FileContentStream时,可以使用此流。

它将立即使用SequentialAccess执行ExecuteReader,以启用来自MSSQL服务器的blob流。 这意味着我们必须小心在使用它时尽快处理流,并且在需要时我们总是懒惰地实例化SqlBlobReader,例如在我们的域对象中使用存储库调用。

我的问题是:

  • 当使用SqlDataReader而不是ORM时,这是在普通旧域对象上实现blob流的聪明方法吗?
  • 我不是ADO.NET专家,实现看起来合理吗?

SqlBlobReader.cs:

using System; using System.Data; using System.Data.SqlClient; using System.IO; namespace Foo { ///  /// There must be a SqlConnection that works inside the SqlCommand. Remember to dispose of the object after usage. ///  public class SqlBlobReader : Stream { private readonly SqlCommand command; private readonly SqlDataReader dataReader; private bool disposed = false; private long currentPosition = 0; ///  /// Constructor ///  /// The supplied sqlCommand must only have one field in select statement, or else the stream won't work. Select just one row, all others will be ignored. 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 } ///  /// Not supported ///  public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } ///  /// Not supported ///  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); } ///  /// Not supported ///  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"; }; } 

有一个错误; 你忽略了用户的args,你应该警惕-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; } 

(否则你写入缓冲区的错误部分)

但一般看起来不错。

请注意.net 4.5现在执行此OOB – SqlDataReader.GetStream()

http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.getstream(v=vs.110).aspx

那太棒了! 谢谢你的内存保护程序。 除了Marc的修复之外,我修改了构造函数以打开连接并在open或execute无法减少调用者中的代码/exception处理的情况下进行处理。 (不知道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; }