2012-01-23 38 views
5

Đối với dự án asp.Net MVC, tôi sẽ cần xử lý các tệp lớn (chủ yếu là 200-300Mo, đôi khi 1Go).Ứng dụng lớp: Lưu trữ tập tin trong dòng phim trong cơ sở dữ liệu

Tôi sẽ lưu trữ chúng trong cơ sở dữ liệu (vì lý do sao lưu/lý do nhất quán).

Tôi lo ngại về vấn đề hiệu suất, vì vậy tôi muốn tránh tất cả những gì có thể để có một mảng byte ở bất cứ đâu trong chương trình, mục tiêu là làm việc với luồng mọi nơi.

Tôi là một ứng dụng phân lớp, chủ yếu có nghĩa là tôi có một vài "DataStore", chịu trách nhiệm kết nối và truy xuất/chèn/cập nhật dữ liệu từ cơ sở dữ liệu.

Vì EF hiện không hỗ trợ Filestream, tôi đang xử lý "Phần tệp" thông qua các yêu cầu Sql đơn giản. Tôi đã đọc một bài viết tốt về FileStream sử dụng ở đây: http://blog.tallan.com/2011/08/22/using-sqlfilestream-with-c-to-access-sql-server-filestream-data/

Và tôi đã một số câu hỏi bổ sung, mà tôi hy vọng bạn có thể giúp tôi/điểm tôi vào hướng tốt:

  • Kể từ khi tôi đã một ứng dụng lớp, một khi tôi đã khởi tạo đối tượng SQLFileStream của tôi, tôi có thể loại bỏ phạm vi SqlCommand/Sql Connection/Transaction không?
  • Nếu không, tôi phải đóng chúng như thế nào?
  • Trong liên kết trước, có một ví dụ minh họa cách sử dụng nó với ASP. Nhưng kể từ khi tôi đang sử dụng ASP.Net MVC, không phải là có một người trợ giúp đó là trực tiếp có thể dòng một tập tin vào trình duyệt? Bởi vì tôi đã tìm thấy nhiều ví dụ về dữ liệu nhị phân trả về cho trình duyệt, nhưng hiện tại, tất cả các ví dụ tôi tìm được về cơ bản giống như Stream.ToArray() để điền vào một mảng byte và trả về trình duyệt. Tôi thấy rằng tôi có thể trả về một số FileStreamResult có thể tham số a Stream. Đó có phải là hướng đi đúng không?

(Tôi hiện không liên quan bằng cách tải lên các tập tin lớn, kể từ khi họ được chèn vào bởi một ứng dụng nặng trong cơ sở dữ liệu)

EDIT

(Xin lỗi vì mã bẩn, nó chỉ không có 50 phương pháp khác nhau ở đây Tôi đã thử thêm một chút và tôi hiện đang mắc kẹt với phần "đã đọc", vì phần tách rời (nơi chúng tôi tạo lớp và nơi chúng tôi sử dụng nó):

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

Nhưng tôi nhận được ngoại lệ tại rdr.GetSqlBinary(1).Value vì GET_FILESTREAM_TRANSACTION_CONTEXT trả về giá trị rỗng. Tôi đã tìm thấy here rằng đây là do giao dịch bị thiếu.

Tôi đã thử với một "TransactionScope" + cuộc gọi .Complete(); của nó. Không thay đổi gì cả.

tôi đã cố gắng để làm một GIAO DỊCH BEGIN như cho thấy trong liên kết theo thời gian:

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

Nhưng nó bị treo trên "ExecuteNonQuery" đầu tiên với ngoại lệ "A transaction that was started in a MARS batch is still active at the end of the batch. The transaction is rolled back." Nhưng đó là truy vấn FIRST được thực hiện!

+0

Tôi có thể nói đây là nhiệm vụ phù hợp hơn với hệ thống tệp, vì bạn đang xử lý tệp. Giả sử trang web/cơ sở dữ liệu được triển khai trên một máy tính Windows, một thay thế để xem xét có thể là Giao dịch NTFS. Nó thậm chí phải tích hợp với các giao dịch khác bằng DTM. Tho thừa nhận rằng có một số của gotcha (ví dụ, hỗ trợ chia sẻ tập tin xấu). 1 vì không sử dụng byte [] :) –

Trả lời

7

Hãy có một ví dụ. Chúng tôi có thể bắt đầu bằng cách xác định hợp đồng mô tả hoạt động mà chúng tôi sẵn sàng thực hiện:

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

Chúng tôi sẽ xem triển khai sau.

Bây giờ chúng ta có thể xác định một kết quả hành động tùy chỉnh:

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

sau đó một bộ điều khiển mà sẽ cho phép chúng tôi hiển thị hình ảnh:

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

và một cái nhìn tương ứng, trong đó chúng ta sẽ hiển thị ảnh trong thẻ <img> sử dụng hành động Photo:

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

Bây giờ là phần cuối cùng của khóa học là thực hiện các kho lưu trữ mà có thể tìm cái gì đó dọc theo dòng:

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

Tất cả những gì còn lại bây giờ là hướng dẫn khung DI yêu thích của bạn để sử dụng PhotosRepositorySql.

Kỹ thuật này cho phép bạn làm việc hiệu quả với các tệp lớn tùy ý vì nó không bao giờ tải toàn bộ luồng vào bộ nhớ.

+0

Oh! Ý tưởng tốt để sử dụng Trình xử lý HTTP tùy chỉnh. Tôi không biết trước khi hành động <...> đại biểu, ý tưởng tốt quá! Tôi sẽ thử ngay bây giờ! – J4N

+0

@ J4N, tôi không sử dụng trình xử lý HTTP tùy chỉnh. Tôi đang sử dụng kết quả hành động tùy chỉnh. –

+0

Có, xin lỗi vì sự nhầm lẫn. Tôi đã hoàn thành thử nghiệm nó và nó hoạt động tốt! Tôi vừa thêm "Tên tệp" vào kết quả hành động. Cảm ơn bạn rất nhiều vì điều này, bạn đã cứu ngày của tôi! – J4N

-1

Cơ sở dữ liệu SQL hoạt động rất xấu với các tệp lớn và cũng có giới hạn kích thước đăng ký. Tôi khuyên bạn nên sử dụng một databse NoSQL (như MongoDB) để lưu trữ các tệp lớn đó. bạn có thể sử dụng hai cơ sở dữ liệu, (SQL cho dữ liệu đơn giản, NoSQL cho các tệp) không có vấn đề gì.

Tôi biết đây không phải là câu trả lời bạn muốn, nhưng là cách tốt nhất để có hiệu suất tốt.

+2

Anh ta hỏi về kiểu dữ liệu SQL Server 2008 FILESREAM được thiết kế đặc biệt cho mục đích này. –

+0

Tôi xin lỗi, nhưng Sql Server 2008 là một hạn chế, tôi không thể sử dụng bất kỳ loại cơ sở dữ liệu nào khác. Và Filestream được thiết kế để lưu trữ tệp lớn, nó chỉ quản lý "con trỏ" tới các tệp thực sự – J4N

0

Kiểm tra câu trả lời này cho một ví dụ về tải thành công file lên đến 4GB: https://stackoverflow.com/a/3363015/234415

Một vài điểm thú vị để nhớ về việc sử dụng Filestream:

  • Các tập tin không được tính là một phần của giới hạn kích thước (do đó, nó hoạt động độc đáo với SQLEXPRESS có giới hạn 10GB cho phiên bản SQL 2008 R2. Vì vậy, bạn có thể có 10 GB dữ liệu và terabyte tệp, tất cả đều miễn phí, điều này thật tuyệt.
  • Bạn phải sử dụng tích hợp ecurity (MSDN), nó không hỗ trợ đăng nhập SQL. Điều này là khó chịu nếu bạn muốn sử dụng chia sẻ lưu trữ!
+0

Cảm ơn bạn đã phản hồi, nhưng nó thực sự không giúp tôi xếp lớp ứng dụng của mình. Đối với các ràng buộc khác, chúng tôi sẽ có một phiên bản tiêu chuẩn SQL Server theo cách – J4N

Các vấn đề liên quan