Dynamics CRM 2011 ではデータベーストランザクション中に実行される次のステップが追加されています。従来のステージPre-validation(10)は引き続き使用できます。従来のPost-Operation(50)は非推奨になっています。

ステージ名 ステージ値
Pre-Operation 20
Post-Operation 40

トランザクションサポートといっても、Pre-Operation,Post-Operation 内で、他のエンティティレコードに対して行った変更はトランザクションのスコープ外として取り扱われると思っていたので、検証してみました。本記事で検証するまでもなく、プラグインのトランザクションの検証はJapan Dynamics CRM Team Blogの"Dynamics CRM 2011 強化された Plug-in"に掲載されています。ただ、個人的に試してみたかったので検証した内容を本記事に記載します。

結論から言うと、トランザクションスコープのステップ内でロールバックを発生させるようにプラグインから例外をスローしたところ、他のエンティティのレコードに対して行った操作もロールバックされました。

1. 検証用のプラグイン実行シナリオ

取引先担当者作成時に、次の処理を行うプラグインを作成します。Dのステップで例外を発生させて、Aの取引先担当者の作成、Cで作成したタスクそしてBで作成した取引先企業がロールバックされることを確認します。

A.取引先担当者をフォームから作成
    →B.取引先担当者Pre-Operationプラグインで取引先企業作成
        →C.取引先企業Post-Operationプラグインでタスク作成
    →D.取引先担当者Post-Operationプラグインでエラー発生

2. プラグインの作成

プラグインの作成におけるdll参照の設定などは [Dynamics CRM 2011]シンプルなプラグインを作ってみるを参照して下さい。

使用したプラグインのソースを掲載します。以下のソース 取引先担当者のPre-Operationで動作し、CreateAccountPlugin は、取引先企業を作成するプラグインです。

namespace TransactionTest
{
    /// <summary>
    /// 取引先担当者が作成されたときに、
    /// 所属取引先企業/上司が未設定の場合に
    /// 自動で取引先企業を作成するプラグイン
    /// </summary>
    public class CreateAccountPlugin : IPlugin
    {
        #region コンストラクタ
        public CreateAccountPlugin() { }
        public CreateAccountPlugin(string unsecure) { }
        public CreateAccountPlugin(string unsecure, string secure) { }
        #endregion

        /// <summary>
        /// 取引先担当者が作成されたときに
        /// </summary>
        /// <param name="serviceProvider"></param>
        public void Execute(IServiceProvider serviceProvider)
        {
            // 各種サービスクラスを取得
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

            if (context.MessageName.Equals("Create", StringComparison.InvariantCultureIgnoreCase)
                && context.PrimaryEntityName.Equals("contact", StringComparison.InvariantCultureIgnoreCase))
            {
                Entity contact = context.InputParameters["Target"] as Entity;
                if (contact.GetAttributeValue<EntityReference>("parentcustomerid") == null)
                {
                    tracingService.Trace("取引先企業を作成します。");
                    // 取引先企業を作成する
                    Entity account = new Entity("account");
                    account.Attributes["name"] = string.Format("テスト企業_{0:yyyyMMdd-HHmmss}", DateTime.Now);
                    IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
                    Guid accountid = service.Create(account);
                    contact.Attributes["parentcustomerid"] = new EntityReference("account", accountid);
                    contact.Attributes["department"] = "部署テスト" + context.IsolationMode.ToString();
                }
            }
        }
    }
}

以下のソースThrowErrorPluginは取引先担当者のPost-Operationで動作し、常に例外をスローするプラグインです。

namespace TransactionTest
{
    /// <summary>
    /// 常にエラーをスローするプラグイン。
    /// 取引先担当者のPost-Operationで動作
    /// させ、トランザクションサポートの検証
    /// を行うために使用する。
    /// </summary>
    public class ThrowErrorPlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            throw new InvalidPluginExecutionException("エラーが発生しました。");
        }
    }
}

以下のソースCreateTaskPluginは取引先企業のPost-Operationで動作し、タスクを作成するプラグインです。

namespace TransactionTest02
{
    public class CreateTaskPlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // 各種サービスクラスを取得
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

            if (context.PrimaryEntityName.Equals("account", StringComparison.InvariantCultureIgnoreCase)
                && context.MessageName.Equals("Create", StringComparison.InvariantCultureIgnoreCase))
            {
                Entity account = context.InputParameters["Target"] as Entity;
                Entity task = new Entity("task");
                task.Attributes["subject"] = string.Format("テストタスク {0}", account.Attributes["name"]);
                task.Attributes["regardingobjectid"] = new EntityReference("account", (Guid) context.OutputParameters["id"]);

                IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
                service.Create(task);
            }
        }
    }
}

以上が使用したプラグインのソースです。CreateTaskPluginとCreateAccountPlugin,ThrowErrorPlugin は別のプラグインアセンブリとして作成しています。

2. プラグインの設定

プラグインアセンブリの構成とプラグインステップの登録を行います。登録内容は下の表の通りです。Plugin Registration Toolを使用したプラグインの登録とステップの設定は [Dynamics CRM 2011]Dynamics CRM 2011 用の PluginRegistrationTool を使ってプラグインを登録してみるを参照ください。

アセンブリ Isolation Mode プラグイン Message Primary Entity Stage Execution Mode Deployment
TransactionTest Sandbox CreateAccountPlugin Create contact Pre-operation Synchronous Server
ThrowErrorPlugin Create contact Post-operation Synchronous Server
TransactionTest02 None CreateTaskPlugin Create account Post-operation Synchronous Server

 

以上で準備が完了です。

あとは、取引先担当者を作成したときに、ThrowErrorPluginのステップが有効時には各レコードが作成されず、無効時には各レコードが作成されることを確認します。

ちなみに、Isolation Modeはそれぞれのアセンブリが Noneや Sandbox の場合等も試しましたが結果は変わりませんでした。

3.まとめ

説明は以上です。プラグインのトランザクションは自身のレコードだけでなく、プラグインから作成したレコードもトランザクションのスコープに含まれることが確認できました。トランザクションサポートのおかげで、2011ではレコード間の整合性を維持しやすくなります。

今回の実験とは直接関係ないのですが、フォームから取引先担当者を作成したときに CreateAccountPluginでは、取引先企業/上司 を設定しています。取引先担当者作成後のフォームを確認すると取引先企業/上司は設定されているのですが、主属性が表示されるフィールドがブランクになってしまいました。データベースのContactBase の ParentCustomerIdName カラムもNULLになっていました。Pre-Validationでプラグインを動作させると主属性が表示されました。この動作は不具合な気がします。本件は別で調べたいと思います。