2012-01-03 7 views
1

私が書いた古いウェブアプリを見ていて、DataTableから4500レコードを読み込むのに約1時間かかるので、CSVファイルに書き込むことができます。私はこれを改善するための何らかの方法が必要だと感じています。注意すべきこのDataTableをより効率的に読んでCSVファイルに書き込むことはできますか?

いくつかのこと:

  • のDataTableが... 376個の列は、少なくとも

    が含まれている、私はExcelのNL列がに変換するものだと思います。私はちょうど列の数を今見上げて、そんなにたくさんあるということを知らなかった。当社のソフトウェアベンダーは、このプロセスの動的SQL文の価値を認識していないため、ソフトウェアのアップグレードによって、必要なものだけを選択するのではなく、列を追加し続けるだけです。私はデータの種類に応じてデータ

  • を生成し、SQLステートメントを変更することはできません

  • 、データが特定の形式でフォーマットする必要が

  • データはありませんコンマなどの特殊文字が含まれてい

  • 遅い部分がデータを読み取っています。 SQLサーバーからデータを取得し、それをCSVに書き込むのは高速です。

ここにコードがあります。私は私がやっていたかわからなかったときにそれを背中に書いた、混乱を許し、私はまだVBで働いていたとき

Function ReadDataTableForCSV(dt as DataTable) 

    Dim sb = New StringBuilder() 
    Dim dataTypes As New Dictionary(Of String, Integer) 

    ' Header Row 
    For i as Integer = 0 to dt.Columns.Count - 1 
     Dim col as DataColumn = dt.Columns(i) 

     Dim t = col.DataType 
     If t is GetType(Boolean) Then 
      dataTypes.Add(i, 1) 
     Else If t is GetType(Decimal) Then 
      dataTypes.Add(i, 2) 
     Else 
      dataTypes.Add(i, 3) 
     End If 

     sb.Append(String.Format("""{0}""", col.ColumnName)) 
     sb.Append(Iif(i = dt.Columns.Count - 1, vbLf, ",")) 

    Next 

    ' Items 
    For Each row as DataRow in dt.Rows 
     For i As Integer = 0 To dt.Columns.Count - 1 
      Select dataTypes(i) 
       Case 1 
       sb.Append(String.Format("""{0}""", CInt(row(i)))) 
      Case 2 
       sb.Append(String.Format("""{0}""", FormatNumber(row(i), 2, , , 0))) 
      Case 3 
       sb.Append(String.Format("""{0}""", row(i))) 
      End Select 

      sb.Append(Iif(i = dt.Columns.Count - 1, vbLf, ",")) 
     Next 
    Next 

End Function 

編集:削除コード問題

+1

最初にまず最初に2つのコードセクションがあります。データテーブルからの読み取り、およびディスクへの書き込みを行います。どちらが遅いですか? (私は間違いなく*ファイルを書いているファイルを整理しています。心配します。単純に 'File.WriteAllText'は1回の呼び出しでそれを行います) –

+0

@JonSkeet申し訳ありません。 DataTableから読み取る部分は遅い部分です。 – Rachel

+0

実際、私は前に誤解されていました。明らかに* DataTable(GetMyDataTableへの呼び出し)を取得し、そこから読み取り、次に書き込みを行う*コードが3つずつ分かれています。間違いなく、それを作成するよりもむしろ遅いデータテーブルからの読み込みですか? –

答えて

2

は、私はそれを書き換えるだろうかです:

  1. はアップフロントのStringBuilderのメモリを割り当てます。

  2. データ型を辞書からバイト配列に変更し、値1と2のみを使用します。値3は0になります。これは配列内の項目のデフォルトになります。

  3. 別のインデックスではなく、列のOrdinalプロパティを使用します。

  4. 項目と行区切り記号のループ内の評価を合理化します。

  5. FormatNumberの代わりにDecimal.ToStringを使用します。

  6. ここでのコードだIIFS(これらはおそらく、コンパイラによって最適化されているが、私は初期のVBの日からまだそれらの警戒心を抱いています)

を削除します。

Function ReadDataTableForCSV(dt As DataTable) 

    Dim sb As New StringBuilder(100000000) 
    Dim dataTypes As Byte() 

    ReDim dataTypes(dt.Columns.Count - 1) 

    ' Header Row 
    For Each col As DataColumn In dt.Columns 
     If sb.Length <> 0 Then 
      sb.Append(",") 
     End If 

     Select Case col.DataType.ToString 
      Case "System.Boolean" 
       dataTypes(col.Ordinal) = 1 

      Case "System.Decimal" 
       dataTypes(col.Ordinal) = 2 

       ' Everything else defaults to 0 

     End Select 

     sb.Append("""").Append(col.ColumnName).Append("""") 
    Next 

    sb.AppendLine() 
    ' or sb.Append(vbLf) 

    ' Items 
    For Each row As DataRow In dt.Rows 
     For i As Integer = 0 To dt.Columns.Count - 1 
      If i <> 0 Then 
       sb.Append(",") 
      End If 

      sb.Append("""") 
      Select Case dataTypes(i) 
       Case 1 
        If CBool(row(i)) Then 
         sb.Append("1") 
        Else 
         sb.Append("0") 
        End If 

       Case 2 
        sb.Append(CDec(row(i)).ToString("F")) 

       Case Else 
        sb.Append(row(i)) 

      End Select 

      sb.Append("""") 
     Next 

     sb.AppendLine() 
     ' or sb.Append(vbLf) 
    Next 

End Function 
+0

今日私が家に帰る時間はほとんどありませんが、私はあなたの提案を明日試して、それがもっと速く走っているかどうかを見極めることに非常に興味があります。ありがとう。 – Rachel

+0

うわー、あなたの提案のうちどれが実行時間を削減したのか分かりませんが、それはほぼ1時間かかってから1分もかからないことになりました!私はStringBuilderのためのメモリを前面に割り当てることができませんでしたが、明らかにそれは大きな問題ではありませんでした。どうもありがとうございます! – Rachel

0

に関連していませんFilehelpersをチェックしてください。これは非常に高速で、引用フィールドを処理し、区切られたファイルと固定幅ファイルの読み書きを簡素化します。

ウォークスルーはCodeprojectにあります。

DapperのようなORMを使用してデータベースから強く型付けされたクラスにレコードを読み込む場合、DelimitedRecordおよびFieldConverter属性で修飾されたORMから強く型付けされたクラスの配列をFilehelpersエンジンに渡すことができます。

SourceforgeとFilehelpersのWebサイトのダウンロードリンクは少し古いです。それは動作しますが、私はSourceforgeから最新のソースを取得することをお勧めします。

EDIT

何も1本のパフォーマンスヒットとして際立っていません。たぶんそれは小さなヒットのコレクションですか?これを撃つ。

  • DataTableを読み込んで列挙するのではなく、直接DataReaderを列挙します。少ないボクシング。
  • 最新バージョンのVB.NETを使用している場合は、IIFではなく3値IFを使用してください。タイプセーフです。
  • より少ないString.Formats。
  • 辞書検索が少なくなりました。タイプチェックをイメージングするのは辞書ルックアップより高速です。誰かがこれに話すことができますか?
  • 暗黙的な型変換が少なくなりました。

これは暗い書き換えのショットです。 :)ここで

Function ReadDataTableForCSV(dr As SqlDataReader) As String 
    Dim fieldCount As Integer = dr.FieldCount 
    Dim sb = New StringBuilder() 

    ' Header Row 
    For i As Integer = 0 To fieldCount - 1 
     sb.AppendFormat("""{0}""", dr.GetName(i)) 
     sb.Append(If(i = fieldCount - 1, vbLf, ",")) 
    Next 

    ' Items 
    While dr.Read() 
     For i As Integer = 0 To fieldCount - 1 
      Dim t As Type = dr.GetFieldType(i) 

      sb.Append("""") 'quoted cell 
      If t Is GetType(Boolean) Then 
       sb.Append(If(dr.GetBoolean(i), "1", "0")) 
      ElseIf t Is GetType(Decimal) Then 
       sb.Append(dr.GetDecimal(i).ToString("#.##")) 
      Else 
       sb.Append(dr(i)) 
      End If 
      sb.Append("""") 'quoted cell 

      sb.Append(If(i = fieldCount - 1, vbLf, ",")) 
     Next 
    End While 

    Return sb.ToString() 
End Function 
+0

私はあなたがASP.NETタグを使用して参照してください。私の記憶がうまく機能するならば、Filehelpersに、ファイルに書き込むのではなく、直接ResponseStreamに書き込むよう伝えることができます。ユーザーは、ダウンロード中のファイルがダウンロードされたときに生成されていることを知らないでしょう。 –

+0

申し訳ありませんが、私は私の質問でより明確にすべきでした。遅い部分は、CSVファイルを書き込むのではなく、 'DataTable'を読み込んでいます。 – Rachel

+0

私は参照してください。あなたはまた、370+の列があり、不当に大きなクラスに変換されると述べました。私の答えはあなたのニーズに合っていません。 :) –

0

を取得している場合データテーブルはボトルネックであり、SQL文を変更することはできず、結果はデー​​タテーブルとして返されます。

結果が返される方法を制御できる場合は、ienumerableを返します。 TはPOCOまたはIDataRecordのいずれかであり、遅延実行を使用します。レコードを消費するときにのみロードするため、メモリ消費量が少なくなります。データの読み込み

この

while(reader.Reader()) 
{ 
    yield return reader; // or reader.ConvertRecordToObject<T>(); //extension method 
} 

のように見えるかもしれません、あなたは、これはまだほとんどされるSQL文の問題を残したcsvfileに

foreach(var record in GetEnumerationOfRecords()) 
{ 
    WriteToCsv(record); 
} 

を書き出すために、結果をforeachすることができますおそらく大部分のパフォーマンス問題を引き起こしますが、それについては何もできません。

+0

問題は明らかにSQL文ではありません。私はいくつかの方法をテストし、パフォーマンスの問題はデータテーブルを読み込むことにあります。データテーブルをループして結果をStringBuilderに書き込むforeachセクションをコメントアウトすると、数秒で実行されます。私の問題はメモリ消費でもありません。パフォーマンスです。ファイルを生成するのに1時間かかることはありません。 – Rachel

+0

オプションが存在する場合は、データテーブルを返さないでください。私はまだ遅延実行を使用することをお勧めします。 –

+0

ありがとうございましたが、DataTableの代わりにDataReaderを使用してみましたが、実行時間が変更されませんでした – Rachel

関連する問題