2016-10-06 8 views
1

2GBまでのファイルが入っていて、何千ものファイルが入っています。ASP.NET MVC4 - ADO.NET - ZipArchivesから多数のファイルをSQLサーバーに保存する

すべてのファイルは、テーブル値のパラメータを取得し、ADO.NETを介して呼び出されるストアドプロシージャを使用して、SQL Server 2014データベーステーブルに別の行として保存されます(ファイルはjpg、pdf、txt、docなど)。 。このテーブルでは、filenameにはvarchar、ファイル自体にはvarbinary(max)が使用されます。

以前は、受信したzipファイルがメモリ内に抽出され、その内容はDictionary<T>に格納され、DB全体を1回の呼び出しで保存しましたが、抽出されたコレクションが2GB以上になる可能性があるため、したがって、辞書オブジェクトはの最大サイズのCLRオブジェクト(2GB)より大きくなっていますこれは.NET 4.5.1でオーバーライドできることがわかりましたが、現時点ではこのオプションを使用したくありません。

この問題を解決するには、ファイルを直接データアクセスクラスに渡して、以下のようにします。基本的に、最大500MBのバッチを作成し、SQL Serverにコミットします。したがって、ラージオブジェクトヒープの管理オブジェクト(データテーブル)のサイズは500MBを超えることはできません。現在のバッチに属していないファイルは、管理されていないメモリに保持されます。

しかし、トランザクションが完了する前にデータが破棄されていると思うので、例外をスローすることなくサイレントに失敗します。しかし、バッチのサイズを2MB程度に大幅に縮小するとうまくいきます。

この問題を回避するにはどうすればよいですか?個々のファイルのサイズが250MBまで増えるので、バッチサイズは500MBにするのが理想です。

Using System.IO.Compression; 

public SaveFiles(int userId, HttpFileCollectionBase files) 
    { 
     try 
     { 
     const long maxBatchSize = 524288000; //500MB 
     var myCollection = namesOfValidFilesBasedOnBusinessLogic; 

     var dataTable = new DataTable("@Files"); 
     dataTable.Columns.Add("FileName", typeof(string)); 
     dataTable.Columns.Add("File", typeof(byte[])); 

     for (var i = 0; i < files.Count; i++) 
     { 
      using (var zipFile = new ZipArchive(files[i].InputStream)) 
      { 
       var validEntries = zipFile.Entries.Where(e => myCollection.Contains(e.name)); 
       long currentBatchSize = 0; 

       foreach (var entry in validEntries) 
       { 
        if (currentBatchSize < maxBatchSize) 
        { 
         currentBatchSize = currentBatchSize + entry.length; 
         using (var stream = entry.Open()) 
         { 
          using (var ms = new MemoryStream()) 
          { 
           stream.CopyTo(ms); 
           dataTable.Rows.Add(entry.Name, ms.ToArray()); 
          } 
         } 
        } 
        else 
        { 
         using (var conn = new SqlConnection(connectionString)) 
         { 
          conn.Open(); 
          using (var cmd = new Sqlcommand("dbo.SaveFiles", conn)) 
          { 
           cmd.CommandType = CommandType.StoredProcedure; 
           cmd.Parameters.AddWithValue("@UserId", userId); 
           cmd.Parameters.AddWithValue("@Files", dataTable); 
           cmd.CommandTimeout = 0; 
           cmd.ExecuteNonQuery(); //control just disappears after this line 
          } 
          dataTable = new DataTable("@Files"); 
          dataTable.Columns.Add("FileName", typeof(string)); 
          dataTable.Columns.Add("File", typeof(byte[])); 
         } 
        } 
       } 
      } 
     } 
    } 
    catch (Exception ex) 
    { 
     throw ex; //Not getting any exception 
    } 
} 
+0

実際のファイルの代わりにファイルパスをアップロードするのはどうですか? – Hackerman

+0

ファイルをデータベーステーブルに直接格納する必要があります。 – Ren

+0

しかし、あなたのチームは、あなたが未来に直面するであろう問題(データベースの成長、断片化、低速クエリ)を既にテーブルに入れていますか? – Hackerman

答えて

1

//コントロールはちょうど私があなたが次の行が実行されることはありませんことを意味していることを前提とするつもりです。このライン

後に消えます。

大量のデータをSql Serverに送信して保存すると、実際にこのデータがサーバーに送信されて処理され、500 MBになる可能性がありますそれが起こるにはしばらく時間がかかる。

コマンドのタイムアウトを200秒などに変更した場合、タイムアウトのために200秒後にSqlExceptionが返ってくることを喜んで賭けています。 0に設定されているため、無期限に待機します。

cmd.CommandTimeout = 200; 

これが望ましくない場合、あなたはそれがXXのMBあたりにかかる時間の量に基づいて、時間とバッチサイズの間の良好なバランスを把握する必要があります。測定可能な唯一の方法は、環境(ネットワーク容量、SQLサーバー負荷、クライアント負荷など)に応じてさまざまなバッチサイズをテストすることです。

+1

ありがとうございます、ええ、あなたは正しいです。それは働いている。ローカルマシンから500MBのデータを保存するのに約35分かかりました。うまくいけば、ライブサーバー上では速くなるはずです。 – Ren

+0

@Ren - あなたはそれが完了するのを見なければならないとうれしいです。回答を受け入れることを検討してください([回答を受け入れる方法](http://meta.stackexchange.com/a/5235)を参照)。これ以上質問があれば教えてください。 – Igor

関連する問題