Это сценарий:
- Мы храним файлы, например, относительно большие документы (10-300 МБ), в BLOB-объектах в нашей базе данных MSSQL.
- У нас очень маленький доменмодели, поэтому мы используем чистый подход SqlDataReader для нашего репозитория вместо ORM, чтобы избежать ненужных зависимостей.
- Мы хотим использовать объекты в контексте сервера на веб-страницах ASP.NET/ASP.NET MVC.
- Мы не хотим временно хранить большие двоичные объекты в байтах [], чтобы избежать чрезмерного использования памяти на сервере
Итак, я занимался реализацией своего собственного SqlBlobReader.Он наследует Stream и IDisposable, и во время создания экземпляра мы должны предоставить SqlCommand, содержащий запрос, который возвращает одну строку с одним столбцом, который, конечно же, является потоком, который мы хотим передать.Тогда мои объекты домена C # могут иметь свойство типа Stream, которое возвращает реализацию SqlBlobReader.Затем этот поток можно использовать при потоковой передаче в FileContentStream в ASP.net MVC и т. Д.
Он немедленно выполнит ExecuteReader с SequentialAccess, чтобы включить потоковую передачу большого двоичного объекта с сервера MSSQL.Это означает, что мы должны быть осторожны, чтобы утилизировать поток как можно скорее, и что мы всегда лениво создаем экземпляр 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";
};
}