Многоуровневое приложение: сохранение файла в файловом потоке в базе данных - PullRequest
5 голосов
/ 23 января 2012

Для проекта asp.Net MVC мне нужно будет обрабатывать большие файлы (в основном 200-300Mo, иногда 1Go).

Я буду хранить их в базе данных (по причинам резервного копирования / согласованности).

Меня беспокоит проблема с производительностью, поэтому я хочу, чтобы все, что я могу, располагало массивом байтов в любом месте программы, поэтому цель состоит в том, чтобы везде работать с потоком.

У меня многоуровневое приложение, что в основном означает, что у меня есть несколько «хранилищ данных», которые отвечают за подключение и извлечение / вставку / обновление данных из базы данных.

Поскольку EF пока не поддерживает FilestreamЯ обрабатываю "Файловую часть" через простые запросы SQL.Я прочитал хорошую статью об использовании файлового потока здесь: http://blog.tallan.com/2011/08/22/using-sqlfilestream-with-c-to-access-sql-server-filestream-data/

И у меня есть несколько дополнительных вопросов, которые, я надеюсь, вы можете мне помочь / указать мне правильное направление:

  • Поскольку у меня многоуровневое приложение, после того как я создал экземпляр объекта SQLFileStream, могу ли я использовать область действия SqlCommand / Sql Connection / Transaction?
  • Если нет, то как мне их закрыть?
  • В предыдущей ссылке есть пример, который показывает, как использовать его с ASP.Но поскольку я использую ASP.Net MVC, нет ли помощника, который может напрямую передавать файл в браузер?Потому что я нашел много примеров возврата двоичных данных в браузер, но сейчас все, что я нашел, делают что-то вроде Stream.ToArray(), чтобы заполнить массив байтов и вернуть его в браузер.Я обнаружил, что могу вернуть FileStreamResult, который может принимать параметр Stream.Это правильное направление?

(в настоящее время я не обеспокоен загрузкой больших файлов, так как они вставляются тяжелым клиентом в базу данных)

EDIT

(Извините за грязный код, здесь только 50 различных методов. Я сделал еще несколько попыток, и в настоящее время я застрял с частью "чтение", потому что разделенчасть (где мы генерируем слой и где мы его потребляем):

        SqlConnection conn = GetConnection();
        conn.Open();
        SqlCommand cmd = new SqlCommand(_selectMetaDataRequest, conn);
        cmd.Parameters.Add(_idFile, SqlDbType.Int).Value = idFile;
        SqlDataReader rdr = cmd.ExecuteReader();
        rdr.Read();
        string serverPath = rdr.GetSqlString(0).Value;
        byte[] serverTxn = rdr.GetSqlBinary(1).Value;
        rdr.Close();
        return new SqlFileStream(serverPath, serverTxn, FileAccess.Read);

Но я получаю исключение в rdr.GetSqlBinary(1).Value, потому что GET_FILESTREAM_TRANSACTION_CONTEXT возвращает ноль. Я нашел здесь , что это из-запропущенная транзакция.

Я попытался с помощью «TransactionScope» + его вызов .Complete();. Ничего не изменилось.

Я попытался выполнить НАЧАЛО СДЕЛКИ, как показано в предыдущей ссылке:

        SqlConnection connection = GetConnection();
        connection.Open();
        SqlCommand cmd = new SqlCommand();
        cmd.CommandText = "BEGIN TRANSACTION";
        cmd.CommandType = CommandType.Text;
        cmd.Connection = connection;
        cmd.ExecuteNonQuery();

        cmd = new SqlCommand(_selectMetaDataRequest, connection);
        cmd.Parameters.Add(_idFile, SqlDbType.Int).Value = idFile;
        SqlDataReader rdr = cmd.ExecuteReader();
        rdr.Read();
        string serverPath = rdr.GetSqlString(0).Value;
        byte[] serverTxn = rdr.GetSqlBinary(1).Value;
        rdr.Close();
        SqlFileStream sqlFileStream = new SqlFileStream(serverPath, serverTxn, FileAccess.Read);
      cmd = new SqlCommand();
        cmd.CommandText = "COMMIT TRANSACTION";
        cmd.CommandType = CommandType.Text;
        cmd.Connection = connection;
        cmd.ExecuteNonQuery();

Но происходит сбой при первом "ExecuteNonQuery" за исключением "A transaction that was started in a MARS batch is still active at the end of the batch. The transaction is rolled back." Но это ПЕРВЫЙ запрос, выполненный!

Ответы [ 3 ]

7 голосов
/ 23 января 2012

Давайте рассмотрим пример. Мы могли бы начать с определения договора, в котором будет описана операция, которую мы готовы выполнить:

public interface IPhotosRepository
{
    void GetPhoto(int photoId, Stream output);
}

Мы увидим реализацию позже.

Теперь мы можем определить результат пользовательского действия:

public class PhotoResult : FileResult
{
    private readonly Action<int, Stream> _fetchPhoto;
    private readonly int _photoId;
    public PhotoResult(int photoId, Action<int, Stream> fetchPhoto, string contentType): base(contentType)
    {
        _photoId = photoId;
        _fetchPhoto = fetchPhoto;
    }

    protected override void WriteFile(HttpResponseBase response)
    {
        _fetchPhoto(_photoId, response.OutputStream);
    }
}

тогда контроллер, который позволит нам показать фото:

public class HomeController : Controller
{
    private readonly IPhotosRepository _repository;

    public HomeController(IPhotosRepository repository)
    {
        _repository = repository;
    }

    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Photo(int photoId)
    {
        return new PhotoResult(photoId, _repository.GetPhoto, "image/jpg");
    }
}

и соответствующий вид, в котором мы собираемся показать фотографию в теге <img>, используя действие Photo:

<img src="@Url.Action("photo", new { photoid = 123 })" alt="" />

Теперь последняя часть, конечно, является реализацией репозитория, который может выглядеть примерно так:

public class PhotosRepositorySql : IPhotosRepository
{
    private readonly string _connectionString;
    public PhotosRepositorySql(string connectionString)
    {
        _connectionString = connectionString;
    }

    public void GetPhoto(int photoId, Stream output)
    {
        using (var ts = new TransactionScope())
        using (var conn = new SqlConnection(_connectionString))
        using (var cmd = conn.CreateCommand())
        {
            conn.Open();
            cmd.CommandText =
            @"
                SELECT 
                    Photo.PathName() as path, 
                    GET_FILESTREAM_TRANSACTION_CONTEXT() as txnToken 
                FROM 
                    PhotoAlbum 
                WHERE 
                    PhotoId = @PhotoId
            ";
            cmd.Parameters.AddWithValue("@PhotoId", photoId);
            using (var reader = cmd.ExecuteReader())
            {
                if (reader.Read())
                {
                    var path = reader.GetString(reader.GetOrdinal("path"));
                    var txnToken = reader.GetSqlBinary(reader.GetOrdinal("txnToken")).Value;
                    using (var stream = new SqlFileStream(path, txnToken, FileAccess.Read))
                    {
                        stream.CopyTo(output);
                    }
                }
            }
            ts.Complete();
        }
    }    
}

Теперь осталось только указать вашей любимой платформе DI на использование PhotosRepositorySql.

Этот метод позволяет эффективно работать с произвольными большими файлами, поскольку он никогда не загружает весь поток в память.

0 голосов
/ 23 января 2012

Проверьте этот ответ для примера успешной загрузки файлов объемом до 4 ГБ: https://stackoverflow.com/a/3363015/234415

Несколько интересных моментов, которые следует помнить при использовании Filestream:

  • Файлы не считаются частью ограничения по размеру (поэтому он прекрасно работает с SQLEXPRESS, который имеет ограничение в 10 ГБ для версии SQL 2008 R2. Таким образом, вы можете иметь 10 ГБ данных и терабайты файлов, все бесплатно , это круто.
  • Вы ДОЛЖНЫ использовать встроенную защиту ( MSDN ), он не поддерживает вход в SQL. Это раздражает, если вы хотите использовать виртуальный хостинг!
0 голосов
/ 23 января 2012

База данных SQL очень плохо работает с огромными файлами, также есть ограничение размера реестра.Я рекомендую использовать базу данных NoSQL (например, MongoDB) для хранения этих огромных файлов.Вы можете использовать две базы данных (SQL для простых данных, NoSQL для файлов). Нет проблем.

Я знаю, что это не тот ответ, который вам нужен, но это лучший способ добиться хорошей производительности.

...