【Salesforce】(Tips) DML currently not allowed エラーへの対応

2016.12.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは植木和樹@上越妙高オフィスです。APEXでDMLを実行すると "DML currently not allowed" というエラーが発生することがあります。この原因と対応について試行錯誤した結果を本日は書き留めておきたいと思います。

当初の実装予定

MFクラウド請求書のAPIで請求書PDFを取得するためには、その請求書のIDが必要になります。つまりSalesforce側で請求書IDを保存しておく必要があります。

MFクラウドで請求書を作成したタイミングでIDをSalesforceに戻しておけばいいのですが、その連携処理の都合でちょっと改修が難しい事情がありました。(複数請求書をまとめてバッチ処理するようになってる)

そこで請求書PDF取得時にSalesforceに請求書IDがあるかを調べ、なかったら一度MFクラウドに問い合わせてIDを得て保存しようと考えました。(SalesforceとMFクラウドは請求書番号で紐付けられるようになってます) しかしSalesforceに保存しようとしたところ"DML currently not allowed"が発生してしまいました。

20161229_sf_mfDisplayPDF1

DML currently not allowed とは

このエラーについてはSalesforceのディスカッションフォーラムに上がっていました。

  1. You attempt DML in a component without the AllowDML=true
  2. You attempt DML in a controller constructor
  3. You attempt DML in a get method in a controller.

今回はControllerのゲッターメソッドを呼び出して(Base64エンコードされた)PDFデータを取得するタイミングで発生しているので(3)の条件に合致してしまったようです。

改修後"You have uncommitted work pending" エラーが発生

ゲッターメソッドの中でDMLができないことが分かったため、ページ初期化のタイミングで請求書IDの有無もチェックすることにしました。 しかし先にあげたとおり、Controllerのコンストラクタの中でもDMLは使えないようです。

そこでVisualforceページを表示する際に実行されるメソッド(<apex:page action="XXXXX">で指定できる)で行うようにしました。 ところが今度はPDFファイルを取得するAPI Callの段階で"You have uncommitted work pending"エラーが発生してしまいました。

20161229_sf_mfDisplayPDF2

これは前回のブログで書いたとおり、DML後はAPI Callが行えないというAPEXの仕様によるものです。

どうもページ初期化 → ゲッターメソッドという流れでトランザクションは継続しているようです。どこかでトランザクションをコミットしたいのでさらに修正を行いました。

最終的な実装

最終的に請求書IDをSalesforceに保存した後には、ページリロードしてトランザクションをコミットさせることにしました。調べたところAPEXではDML操作(insert, update, delete)後、明示的にcommitする方法がないようです。ページ表示終了 = トランザクション終了 になるようですね。

20161229_sf_mfDisplayPDF3

まとめ

SalesforceのDMLまわりはいろいろクセがあるようです。

  • DML(insert, update, delete)の後はAPI Callを行わない
  • コンストラクタやゲッターメソッドの中ではDMLは行わない

この2点を守っておけば落とし穴にはまることは少なくなるかと思います。この原因調査と修正に3日費やしました(泣)