10 万行・100 万行を扱う場合の具体的な手順です。前提となる性能特性は 性能とメモリ にまとめてあります。
まず押さえておくこと — シートを大きく作ること自体はコストではありません。 104 万行のシートを作っても、値を書くまでメモリはほぼゼロです。遅くなる原因は ほぼ常に「書き方」のほうにあります。
全件をメモリに載せる場合
数十万行までなら、素直に全部書いてしまうのが最も簡単で速い方法です。
using unvell.ReoGrid.Core;
using unvell.ReoGrid.Core.Data;
using unvell.ReoGrid.Core.Style;
// 1. 書式は行・列単位で先に決める
ws.SetColumnStyle(1, new StyleRecord { TextAlign = HAlign.Right });
// 2. 値を書く。型が分かっているなら SetText / SetNumber が最も軽い
for (int i = 0; i < rows.Count; i++)
{
ws.SetText(i, 0, rows[i].Name);
ws.SetNumber(i, 1, rows[i].Amount);
}
// 3. 数式は最後に。まとめて書いてから 1 回だけ再計算する
ws.SetFormula(rows.Count, 1, $"SUM(B1:B{rows.Count})");
ws.Recalculate();
順序が重要です。
- 書式を先に、行・列単位で。 セルのループで書式を設定すると、セル数ぶんの エントリが実体化してメモリが跳ね上がります
- 値は型付きの
Set*で。SetObjectValueは型判定を通るぶん遅くなります - 数式は最後に、
Recalculate()は 1 回だけ。SetFormulaはそのセルと依存先を その場で再計算します
この 3 点を守れば、10 万行の書き込みは通常 1 秒未満で終わります。
表示された行だけを読み込む
DB に数百万件あり、全部をメモリに載せたくない場合は IRowDataSource を実装します。
画面に映る行だけが読み込まれます。
/// <summary>表示された行だけを DB から取り出してシートに載せる。</summary>
public sealed class QueryRowSource : IRowDataSource
{
private readonly Worksheet _sheet;
private readonly HashSet<int> _loaded = new();
public QueryRowSource(Worksheet sheet) => _sheet = sheet;
public void EnsureRows(int firstRow, int lastRow)
{
for (int r = firstRow; r <= lastRow; r++)
{
if (!_loaded.Add(r)) continue; // 既に用意済みの行は読み飛ばす
foreach (var (col, value) in FetchRow(r))
_sheet.SetObjectValue(r, col, value);
}
}
private static IEnumerable<(int Col, object? Value)> FetchRow(int row)
=> [(0, $"row {row}")]; // 実際はここで DB を引く
}
シートに割り当てます。
ws.RowDataSource = new QueryRowSource(ws);
// コントロールは表示直前に可視範囲ぶんを自動で呼ぶ。
// ヘッドレスや先読みしたいときだけ明示的に呼ぶ
ws.EnsureRowsLoaded(0, 99);
コントロールは描画の直前に、可視範囲ぶんの EnsureRowsLoaded を毎回呼びます。
既に用意した行は読み飛ばす実装にしておいてください(上の例の _loaded がそれです)。
そうしないとスクロールのたびに DB を引くことになります。
行数(AddWorksheet の rows)は、実際の件数に合わせて先に決めておきます。
スクロールバーの長さがこれで決まります。
向いている場面・向いていない場面
| 向き | |
|---|---|
| 一覧を眺める・スクロールする | ◎ |
| 特定の行を検索して飛ぶ | ○(飛び先を EnsureRowsLoaded してから MoveTo) |
| 全体の集計を数式で出す | ✗ 読み込まれていない行は空セル扱いになる |
| ソート・フィルタ | ✗ シート上のデータしか見ない |
集計・ソート・フィルタが要るなら、全件を載せるか、DB 側で処理してから 結果だけをシートに出してください。
巨大な XLSX を開く
ファイル側も部分読み込みにできます。シートは即座に使える状態になり、 セルは描画されたぶんだけファイルから読み込まれます。
using var book = XlsxReader.OpenVirtual("large.xlsx", SheetLoadMode.OnDemand);
control.LoadWorkbook(book.Workbook); // そのままコントロールへ渡せる
VirtualWorkbook は IDisposable です。ファイルとシートごとの一時ファイルを
開いたまま保持するため、ワークブックを差し替えるときや終了時に破棄してください。
破棄した後にアクセスすると、セルは空として読まれます。
保存・書き出しの前には MaterializeAll() を呼んでください。 呼ばずに書き出すと、
その時点で読み込まれていた範囲しか出力されません。
book.MaterializeAll(); // 全シート・全行を実体化する
XlsxWriter.Write(book.Workbook, "out.xlsx");
MaterializeAll() は全件をメモリに載せるため、通常読み込みと同じメモリを使います
(XLSX 入出力)。
書き出しはストリーミング
XLSX の書き出しは、ワークブック全体を XML として組み立ててから書くのではなく、 逐次書き出します。行数に比例したメモリしか使いません。 100 万行を書き出しても、メモリ使用量はシートが載っているぶんだけです。
CSV も同様です。
描画は件数に依存しない
シートに何行あっても、描画されるのは画面に映っているセルだけです。 100 万行のシートでもスクロールの滑らかさは変わりません。
同じ理由で、外観のカスタマイズ や ウィンドウ枠の固定 を使っても件数によるコストはかかりません。
詰まったときに見るところ
| 症状 | よくある原因 |
|---|---|
| 書き込みが遅い | セルのループで書式を設定している |
| メモリが想定より多い | 同上、または少しずつ違う書式を大量に作っている |
| 初回表示が遅い | 0 .. RowCount を素朴に走査している |
| スクロールが引っかかる | EnsureRows が毎回データを取りに行っている |
ws.DistinctStyleCount が想定より大きい場合は書式の作りすぎです。