DataTableの内容をエクセルにコピーする方法

アプリでデータをエクセルへコピーするときは注意すべきことがある。エクセルではデータが自動変換される。
  • 全角数字「12345」は半角数字「12345」に変わる
  • 頭に「0」が付いている「00012345」は「12345」に変わる
  • 日付「2017/7/7」が数値に変わる(ことがある)
  • 「-」でつながった数字「7-7」が日付の「7/7」に変わる
エクセルにデータをコピーするときは、上記のことに気を付けないといけない(場合もある)。数字だけコピーしてOKなら、悩むことは無い。(2018/7/25追記)

エクセルにデータをコピーする方法は、MSDNにまとめてある。いろいろな方法をこれでもかという具合にまとめてある。書いた人の気合を感じる。
VB6のころは、レコードセットをエクセルにコピーするメソッドがあったような記憶がある。しかし悲しいことに、上の記事から10年経とうとしているのに、DataTableにはエクセルへデータをコピーするメソッドは追加されていない。

解決策

冒頭に書いた注意点を避けるにはどうするか。以下のようにすると割とうまくいくことに最近気がついた。
  1. エクセルのワークシートを作って、タイトル行をコピーする
  2. データが文字列のときは、エクセルの書式設定を「文字列」にしてコピーする。
    これで「12345」も「00012345」も「7-7」もそのままコピーされる。
  3. 日付データなら書式設定を「日付」にしてコピーする。
    これで日付データが数値に化けることは無い。
  4. それ以外のデータは、そのままコピーする。
DataTableをコピーするときは、列ごとにデータの型があるはずなので、列ごとに書式設定をすればよい。

たとえば、A列は文字列、B列は日付の場合は下記の通り。エクセルの列は1始まりなのに注意。

  1.    ws.Columns[1].NumberFormatLocal = "@";
  2.    ws.Columns[2].NumberFormatLocal = "yyyy/m/d";

ただし、書式設定は割と時間がかかるので、数百列も設定すると大変かも。

サンプルソース

dtはデータテーブルの変数で、この内容をエクセルのシートに丸ごとコピーしたい。

まずは、2次元の配列を用意して、値をコピーする。

  1. object[,] rzData = new object[dt.Rows.Count, dt.Columns.Count];
  2.  
  3. for (int i = 0; i < dt.Rows.Count; i++)
  4. {
  5.     for (int j = 0; j < dt.Columns.Count; j++)
  6.     {
  7.         rzData[i, j] = dt.Rows[i][j];
  8.     }
  9. }

次に書式。1列ずつ設定してもよいのだけど、書式も2次元の配列を用意して、コピーする方法でやってみる。データが化けるのは文字列と日付の場合なので、そのデータだけ書式を設定する。他は設定無し。

  1. /// セルの書式を設定する
  2. /// 文字列は文字列としておかないと「3-1」が日付値に化けたりする
  3. object[,] mmFormat = new object[dt.Rows.Count, dt.Columns.Count];
  4.  
  5. for (int j = 0; j < dt.Columns.Count; j++)
  6. {
  7.     DataColumn co = dt.Columns[j];
  8.     string t = "";
  9.  
  10.     if (co.DataType == typeof(string))
  11.     {
  12.         t = "@";
  13.     }
  14.     else if (co.DataType == typeof(DateTime))
  15.     {
  16.         t = "yyyy/m/d";
  17.         /// t = "yyyy/m/d h:mm:ss";  /// 時刻の場合はこちら
  18.     }
  19.     if (t != "")
  20.     {
  21.          for (int i = 0; i < dt.Rows.Count; i++)
  22.         {
  23.             mmFormat[i, j] = t;  // 行数分だけ設定する
  24.         }
  25.     }
  26. }

最後に、セルの範囲を指定して、まとめて設定する。1セルごとにデータをコピーして書式を設定すると、毎回エクセルにアクセスするので遅くなる(と思う)。まとめて1回で済ますのだ。

  1. // データをコピーする
  2. string pos1 = mmUtils.GetCellAddressA1Style(startRow, startColumn);
  3. string pos2 = mmUtils.GetCellAddressA1Style(startRow + dt.Rows.Count - 1, startColumn + dt.Columns.Count - 1);
  4. sysExcel.Range ra = ws.get_Range(pos1, pos2);
  5. ra.NumberFormatLocal = mmFormat;
  6. ra.Value2 = rzData;

GetCellAddressA1Styleは「1,1」を渡すと「A1」というセル位置を返すメソッドということで。

コメント

このブログの人気の投稿

varchar をデータ型 numeric に変換中に、算術オーバーフロー エラーが発生しました。