2012-01-23 13 views
5

W przypadku projektu asp.Net MVC będę musiał obsługiwać duże pliki (głównie 200-300Mo, czasami 1Go).Aplikacja warstwowa: Przechowuj plik w strumieniu filestream w bazie danych

Będę przechowywać je w bazie danych (z powodów związanych z kopiami bezpieczeństwa/spójności).

Jestem zaniepokojony problemem z wydajnością, więc chcę uniknąć wszystkiego, co możliwe, aby mieć cały szereg bajtów w dowolnym miejscu programu, więc celem jest praca ze strumieniem w dowolnym miejscu.

Mam warstwę aplikacji, co oznacza, że ​​mam kilka "DataStore", które są odpowiedzialne za łączenie i pobieranie/wstawianie/aktualizowanie danych z bazy danych.

Ponieważ EF nie obsługuje teraz Filestream, zajmuję się "Częścią pliku" za pomocą prostych zapytań Sql. Czytałem artykuł na dobre wykorzystanie FILESTREAM tutaj: http://blog.tallan.com/2011/08/22/using-sqlfilestream-with-c-to-access-sql-server-filestream-data/

i mam kilka dodatkowych pytań, które mam nadzieję, że może mi pomóc/wskaż mnie dobrym kierunku:

  • odkąd aplikacja warstwowa po utworzeniu obiektu SQLFileStream, czy mogę zlikwidować zasięg połączenia SqlCommand/Sql/Transaction?
  • Jeśli nie, to jak mam je zamknąć?
  • W poprzednim linku znajduje się przykład pokazujący, jak używać go w ASP. Ale skoro używam ASP.Net MVC, czy nie ma pomocnika, który mógłby bezpośrednio przesłać plik do przeglądarki? Ponieważ znalazłem wiele przykładów danych binarnych zwracanych do przeglądarki, ale na razie wszystkie znalezione przykłady sprawiają, że w zasadzie wypełniają tablicę bajtów i zwracają ją do przeglądarki. Zauważyłem, że mogę zwrócić wartość FileStreamResult, która może przyjąć parametr o wartości Stream. Czy to właściwy kierunek?

(nie jestem obecnie objęty przesyłania dużych plików, ponieważ są one wstawiane przez ciężki klienta w bazie danych)

EDIT

(przepraszam za brudną kodu, to tylko nie mam tutaj 50 różnych metod. Zrobiłem jeszcze kilka prób i obecnie utknąłem w części "przeczytanej", ponieważ oddzielona część (gdzie generujemy warstwę i gdzie ją konsumujemy):

 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); 

Ale otrzymuję wyjątek pod adresem rdr.GetSqlBinary(1).Value, ponieważ funkcja GET_FILESTREAM_TRANSACTION_CONTEXT zwraca wartość null. Znalazłem here, że jest to spowodowane brakującą transakcją.

Próbowałem z połączeniem "TransactionScope" + jego .Complete();. Nic nie zmienia.

Próbowałem zrobić transakcję BEGIN jak pokazał w poprzednim linku:

 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(); 

Ale to zawiesza się na pierwszym "ExecuteNonQuery" z wyjątek "A transaction that was started in a MARS batch is still active at the end of the batch. The transaction is rolled back." Ale to PIERWSZE zapytanie zostało wykonane!

+0

Powiedziałbym, że jest to zadanie bardziej odpowiednie dla systemów plików, ponieważ mamy do czynienia z plikami. Zakładając, że lokacja/baza danych jest wdrażana na komputerze z systemem Windows, jedną z opcji alternatywnych może być Transactional NTFS. Powinien nawet zintegrować się z innymi transakcjami za pomocą DTM. Trzeba przyznać, że są tam pewne błędy (np. Złe wsparcie dla plików). +1 dla nie używania bajtu [] :) –

Odpowiedz

7

Miejmy przykład. Możemy zacząć od zdefiniowania umowy opisującej operację, którą chcemy wykonać:

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

Zobaczymy implementację później.

Teraz możemy zdefiniować wynik zwyczaj Działanie:

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); 
    } 
} 

następnie kontroler który pozwoli nam pokazać zdjęcie:

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"); 
    } 
} 

i odpowiedni widok, w którym mamy zamiar pokazać zdjęcie w tagu <img> przy użyciu akcji Photo:

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

Teraz ostatnia część jest oczywiście realizacja repozytorium, które może wyglądać wzdłuż linii:

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(); 
     } 
    }  
} 

Pozostaje teraz jest poinstruować swój ulubiony ramy DI do używania PhotosRepositorySql.

Ta technika umożliwia wydajną pracę z dowolnymi dużymi plikami, ponieważ nigdy nie ładuje całego strumienia do pamięci.

+0

Och! Dobry pomysł na użycie niestandardowego handlera HTTP. Nie wiedziałem przed delegatem Akcji <...>, to także dobry pomysł! Spróbuję tego teraz! – J4N

+0

@ J4N, Nie używam niestandardowej procedury obsługi HTTP. Używam niestandardowego wyniku akcji. –

+0

Tak, przepraszam za zamieszanie. Skończyłem testowanie i działa dobrze! Właśnie dodałem "Filename" do wyniku akcji. Bardzo ci za to dziękuję, uratowałeś mój dzień! – J4N

-1

Baza danych SQL działa bardzo źle przy dużych plikach i istnieje również limit wielkości rejestru. Zalecam użycie bazy danych NoSQL (takiej jak MongoDB) do przechowywania tych dużych plików. możesz użyć dwóch baz danych, (SQL dla zwykłych danych, NoSQL dla plików), bez problemu.

Wiem, że nie jest to odpowiedź, której potrzebujesz, ale jest to najlepszy sposób na uzyskanie dobrej wydajności.

+2

Pyta o typ danych FILESREAM programu SQL Server 2008, który został specjalnie zaprojektowany do tego celu. –

+0

Przykro mi, ale Sql Server 2008 jest ograniczeniem, nie mogę użyć żadnego innego typu bazy danych. I Filestream jest przeznaczony do przechowywania ogromnego pliku, tylko zarządza "wskaźnikiem" do prawdziwych plików – J4N

0

Sprawdź tę odpowiedź za przykład skutecznie pobierania plików do 4GB: https://stackoverflow.com/a/3363015/234415

Kilka interesujących punktów, aby pamiętać o używaniu Filestream:

  • Pliki nie liczą się jako część limit rozmiaru (więc dobrze pasuje do SQLEXPRESS, który ma limit 10 GB dla wersji SQL 2008 R2, więc możesz mieć 10 GB danych i terabajtów plików, wszystko za darmo, co jest fajne
  • Musisz użyć zintegrowanego s ecurity (MSDN), nie obsługuje logowania SQL. Jest to denerwujące, jeśli chcesz korzystać z hostingu współdzielonego!
+0

Dziękuję za odpowiedź, ale to naprawdę nie pomaga mi w warstwowaniu mojej aplikacji. W przypadku innych ograniczeń, będziemy mieli standardową edycję SQL Server po drodze – J4N

Powiązane problemy