はじめに
Salesforce の Visualforce ページで CSV ダウンロード機能を実装した際、「DML currently not allowed」 というエラーに遭遇することがあります。
特に
- ページのコンストラクタでデータの更新(Database.update() など)を呼び出している
- readOnly="true" なページで DML を実行している
- getContent() などでVFページを呼び出している
といったケースで、このエラーが出ることがよくあります。
本記事では、実際に遭遇した 「DML currently not allowed」エラー をどのように回避して要件を満たしたか、そのアプローチをまとめます。
今回の課題
「VisualforceページでCSVをダウンロードする前後にレコードを更新したい」
具体的には、CSV をダウンロードしたタイミングで 出力したレコードに出力済(仮にDownloadedFlag__cと定義する) などを true に更新する要件を実装しました。
初期実装では、Visualforce コントローラクラスの コンストラクタ内 で SOQL 取得と DML をまとめて書いてしまっていた
その結果、「DML currently not allowed」 例外が発生してページがエラーになる状況。
なぜエラーになるのか?
Salesforce では、Visualforceページの初期読み込み(デフォルトGETリクエスト) が 読み取り専用モード(Read-Only モード) になる場合があります。
代表的な例:
-
<apex:page>
タグで readOnly="true" を指定している - PageReference.getContent() や renderAs などでPDF/CSV出力をすると自動的に読み取り専用になる
この「読み取り専用モード」下で DML(Insert、Update、Delete など)を行うと、
「DML currently not allowed」 というエラーが発生する仕組みになっています。
解決策
ポイントは「コンストラクタやページ初期読み込み時に DMLしない」こと です。
つまり、CSV ダウンロード用のロジックと DML 更新ロジックを別のタイミング・メソッドに分ける と、エラーを回避できます。
今回のアプローチ
-
コンストラクタで「表示用 or CSV出力用データ」の取得のみ行う
コンストラクタでは DML は行わず、「どのレコードをダウンロード対象にするか?」などの判定や、必要なレコードの SOQL 取得だけを行う。 -
action メソッド で DML を実行
Visualforce には<apex:page action="{!methodName}" />
という仕組みがあります。
ページ読み込み時に自動的に呼ばれるメソッドですが、コンストラクタ実行後のアクション なので、ここでなら DML が許される状況を作れる場合があります。
※注意: もし readOnly="true" が明示的に指定されていると、やはり DML は禁止されるので、その場合は readOnly="false" にするか、別のアプローチを検討してください。
- コード例(ざっくりイメージ)
<!-- CSVダウンロードページ -->
<apex:page
controller="DownloadCSVController"
readOnly="false" <!-- 必要に応じてfalseにする -->
action="{!doUpdateRecords}"
contentType="text/csv;charset=Shift_JIS;#somefilename.csv"
language="ja"
>
"ヘッダー1","ヘッダー2","ヘッダー3"
<apex:repeat value="{!csvDataList}" var="row">
"{!row.col1}","{!row.col2}","{!row.col3}"
</apex:repeat>
</apex:page>
public with sharing class DownloadCSVController {
public List<YourObject__c> csvDataList { get; set; }
// コンストラクタ:データの取得だけ
public DownloadCSVController() {
// 例: CSV出力に必要なデータだけ Query
csvDataList = [
SELECT Id, Name, ... FROM YourObject__c
WHERE ...
// ※この時点ではDMLしない
];
}
// actionメソッド:更新などのDMLを行う
public PageReference doUpdateRecords() {
// CSV ダウンロード直前にフラグを立てたい、など
for (YourObject__c rec : csvDataList) {
rec.DownloadedFlag__c = true;
}
update csvDataList; // ← DMLを実行
// ページ遷移(本ページ自身を再読み込み)しない場合は null を返す
return null;
}
// Visualforce 側の <apex:repeat> で CSV用データを表示
}
上記のように constructor(データ取得) → action(更新) → CSV描画 の順序で実行されれば、DML currently not allowed は発生しません。
別の方法・応用
- ダウンロード前か後に別ページ or 別ApexメソッドでDML する
たとえば LWC や Aura コンポーネントで事前に DownloadedFlag__c を更新してから CSV ページにリダイレクトする方法。
またはダウンロードが終わってからボタン押下などで DML する方法。 - Visualforce をやめて LWC + Apex のみで CSV 作成・ダウンロード
Apex 側で Blob の CSV データを生成し、LWC で Base64 受け取り → download する手段もあります。
ただし、非常に大量のデータの場合は Visualforce の readOnly="true" による SOQL 行数拡張 (最大1,000,000行まで) が便利なため、要件次第です。
まとめ
- 「DML currently not allowed」エラーが起きる理由
- Visualforce ページが 読み取り専用モード で実行されており、コンストラクタや初期描画フェーズでは DML が禁止されているから。
- 回避ポイント
- 「CSV生成とDMLを同じリクエスト(コンストラクタ)でやらない」
- 別メソッド (action など) に分けて DML する ことで解決できる。
- メリット
- これで コンストラクタ でのDMLを排除しつつ CSVデータの取得 & Update の両方が可能になる。
- readOnly="false" にしたり、事前に別アクションでDMLを済ませておけば、ページ描画時にデータも更新され、要件を満たせる。
- メリット
- 別メソッド (action など) に分けて DML する ことで解決できる。
- 「CSV生成とDMLを同じリクエスト(コンストラクタ)でやらない」
同じような課題(ダウンロード時にフラグを立てたい等)に直面した方の参考になれば幸いです。