0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Visualforce で CSV ダウンロード& DML 実行時に「DML currently not allowed」を回避する方法

Posted at

はじめに

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 更新ロジックを別のタイミング・メソッドに分ける と、エラーを回避できます。

今回のアプローチ

  1. コンストラクタで「表示用 or CSV出力用データ」の取得のみ行う
    コンストラクタでは DML は行わず、「どのレコードをダウンロード対象にするか?」などの判定や、必要なレコードの SOQL 取得だけを行う。

  2. 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 は発生しません。

別の方法・応用

  1. ダウンロード前か後に別ページ or 別ApexメソッドでDML する
    たとえば LWC や Aura コンポーネントで事前に DownloadedFlag__c を更新してから CSV ページにリダイレクトする方法。
    またはダウンロードが終わってからボタン押下などで DML する方法。
  2. 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を済ませておけば、ページ描画時にデータも更新され、要件を満たせる。

同じような課題(ダウンロード時にフラグを立てたい等)に直面した方の参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?