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

Gei*_*sen 9 .net c# sqldatareader stream

这是场景:

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

所以我一直在做的是实现我自己的SqlBlobReader.它继承了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
{
   /// <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();
      }

   }

}
Run Code Online (Sandbox Code Playgroud)

在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);
  }
Run Code Online (Sandbox Code Playgroud)

在DocumentFile.cs中:

  public Stream GetStream()
  {
     return repository.GetDocumentFileStream(Id);
  }
Run Code Online (Sandbox Code Playgroud)

在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";
               };
  }
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 7

有一个错误; 你忽略了用户的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);
  }
Run Code Online (Sandbox Code Playgroud)

应该是:

  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;
  }
Run Code Online (Sandbox Code Playgroud)

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

但一般看起来不错.