在使用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; }