Это сценарий:Как сделать потоки из BLOB доступны в простых старых объектах C# при использовании SqlDataReader?
- Мы храним файлы, например. относительно большие документы (10-300 МБ), в блоках в нашей базе данных MSSQL.
- У нас очень маленькая модель домена, поэтому мы используем чистый метод SqlDataReader для нашего репозитория, а не ORM, чтобы избежать ненужных зависимостей.
- Мы хотим использовать объекты в контексте сервера на веб-страницах ASP.NET/ASP.NET MVC.
- Мы не хотим, чтобы временно хранить сгустки в байт [], чтобы избежать использования памяти на сервере
Так что я делал это, чтобы реализовать свой собственный SqlBlobReader. Он наследует Stream и IDisposable, и во время создания экземпляра мы должны предоставить SqlCommand, содержащий запрос, который возвращает одну строку с одним столбцом, который является блобом, который мы хотим передать, конечно. Тогда мои объекты домена C# могут иметь свойство типа Stream, которое возвращает реализацию SqlBlobReader. Этот поток можно затем использовать при потоковой передаче в FileContentStream в ASP.net MVC и т. Д.
Он немедленно сделает ExecuteReader с SequentialAccess, чтобы включить потоковое воспроизведение blob с сервера MSSQL. Это означает, что мы должны быть осторожны, чтобы избавиться от потока ASAP при его использовании и что мы всегда лениво инстанцируем SqlBlobReader, когда это необходимо, например. используя вызов репозитория внутри наших объектов домена.
Мой вопрос тогда:
- Это умный способ достижения потоков сгустков на простых старых доменных объектов при использовании SqlDataReader вместо ОРМ?
- Я не эксперт 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";
};
}
Для справки: POCO = Plain Old CLR Object. Not Plain Old C# Object :) – 2010-12-09 09:55:52
Да, я просто очень C#, пока не получаю F # под моим поясом. Да, да. – 2010-12-09 10:09:12