レコードの同時編集性について理解する

Dynamicsのレコード更新についての備忘録です。
オンラインながらも、エンドユーザが気にしていたこともあって調べてみました。

スポンサーリンク

オンラインなので排他制御はない

Dynamicsはオンライン上で動作する仕組みのため、ネットワークに繋がっているユーザであれば誰しもがアクセスできるような仕組みになっています。
当然、異なるユーザが同じレコードを見ることも編集することもあり得るでしょう。

同じレコードを参照するだけならまだしも、更新した時にに画面を開いた時と更新をかけた時でレコードの状態が変わってしまうのはよくある話です。
Dynamicsではこの排他制御について、自動的にロックをかける仕組みはないため、こういった場合は大抵後から更新されたレコードが優先されることになります。
オンプレミス型は試したことがありませんが、ネットワークが局所的になっただけで多分同じ仕組みでしょう。

同時編集の検証

まずは、シンプルなエンティティで同時編集について検証しましょう。

まずは、端末(ブラウザでも可)を2つ用意して、同じレコードを参照します。
レコードを開いた時の状態はこのような状態。
更新前のレコード

ここで、片方の端末(ブラウザ)はこのように編集します。
こちらを端末Aとしましょう。
端末Aのレコード更新

もう一方はこのように編集します。
こちらを端末Bとします。
端末Bのレコード更新

端末Aを保存した後に、端末Bを保存します。
更新後のレコード

更新後のレコードはこうなりました。

確かに、更新するフィールドが重複するところは後勝ちで更新されているんですが、更新が重ならないフィールドについては、端末Aの内容がキープされていますね。
これがもし完全な全更新であれば、テキスト1の値が「a」になっていないとおかしい訳ですからね。
どうやら、Dynamicsではレコードは全更新ではなく、差分更新になっているみたいです。

排他制御こそないものの、矛盾が起こりづらいように考慮はされているみたいです。

それでも排他をかけたい場合は

さて、部分更新で保存されることは分かりましたが、結局のところ排他問題が解消された訳ではありません。

更新が重複されるフィールドは後勝ちのままですからね。

もし、後勝ちを避けたい場合には、排他制御は自力で作成するしかないようです。

簡単なスクリプトを組んで、排他制御を作ってみます。

今回は保存時にロード時の更新日時とセーブ時の更新日時を比較して差分があれば更新しないようなスクリプトを作っています。

スクリプトのソースコードはこんな感じ。

// 保存フラグ
var saveFlg = false

var Sdk = window.Sdk || {};
(function () {
    // OnSaveイベント
    this.formOnSave = function (executionContext) {
	if (saveFlg == false){
	    var formContext = executionContext.getFormContext();

	    // 保存処理を一時停止
            executionContext.getEventArgs().preventDefault();

	    // WebAPIを使用して最新のレコードを取得
	    Xrm.WebApi.retrieveRecord(formContext.data.entity.getEntityName(), formContext.data.entity.getId()).then(
    	    function success(result) {
		// 更新日の比較
		var date1 = new Date(result.modifiedon);
		var date2 = new Date(formContext.getAttribute("modifiedon").getValue());
		// 日時分で比較
		var diffTime = Math.floor((date1 - date2) / (60 * 1000))
		if(diffTime == 0){
                    // 更新日が一緒ならフラグを立てて再度OnSaveイベントを走らせる
		    saveFlg = true;
		    formContext.data.save();
		}
		else{
		    formContext.ui.setFormNotification("別のブラウザで保存されています", "WARNING", formContext.data.entity.getId());
		}
    	    },
	    // retrieveRecordエラー
	    function (error) {
		formContext.ui.setFormNotification(error.message, "ERROR", formContext.data.entity.getId());
    	    }
	    );
    	}
	else{
	    saveFlg = false;
	}
    }
}).call(Sdk);

ポイントは2点。

1点目はデータの取得タイミング。
ロード時はコンテキストから取得し、セーブ時はXrm.WebApi.retrieveRecordを使用して最新のレコードを取得しています。

2点目は保存イベントの制御。
executionContext.getEventArgs().preventDefault()で保存処理を中断させています。
その後の、formContext.data.save()でもう一度onSaveメソッドが走るので、フラグを使用して2回目はexecutionContext.getEventArgs().preventDefault()を経由せずに保存処理を走らせるようにしています。

なお、コンテキスト上からは日時分の単位でしかとれない(秒はとれない)ので、更新日の比較は秒以降を除いた分を比較しています。

ただ、こちらのコードはシンプルに作ったので、完璧な排他制御はではなく、同じ日時分でタイミングで更新をかけた時などではもうちょっと厳密なチェックが必要です。
とりあえず、排他制御処理の参考まで。

あとはスクリプトをフォームのOnSaveイベントに組み込んで。
排他制御処理をOnSaveイベントに追加

実際に別端末(ブラウザ)で更新させたあとに保存をすると、このようにエラーが出て保存処理が中断されます。
排他時のエラー

まとめ

  • レコードは全更新ではなく部分更新
  • 更新するフィールドが重なる時は後勝ちとなる
  • 排他処理を行う場合はスクリプトなどで自作が必要
Dynamics
スポンサーリンク

コメント