Dynamics CRM 2011 では、レコード作成時に同期的にマッチコードが作成されるらしく、Webクライアントから重複ルールに一致するレコードを連続して登録するとレコードの重複が検出されます。

Dynamics CRM 4.0時代は重複が検出されるようになるまでに、5分ごとに非同期で実行されるマッチコードの更新ジョブを待つ必要がありました。

4.0の時代でも。インポートや、プログラムからレコードを登録、更新する場合は、同期的にマッチコードを作成し、直後に登録したレコードを重複レコードとして検出することができました。

残念ながら、4.0時代と同様、プログラムでほぼ同時に重複ルールに一致するレコードは登録した場合、重複が検出されずレコードが登録できてしまいました。今回は検証用のプログラムと検証結果を覚書として記載します。

検証に使用した環境は次のとおりです。

  • Windows Server 2008 R2 上に構築した Dynamics CRM 2011 UR 3.0
  • CPUのコア数は2

今回同時実行処理には PLINQ を使用しています。サンプルプログラムを検証する場合はCPU(コア)数が2以上の環境で実行してください。1CPUの場合処理がシーケンシャルに実行されて重複レコードを登録することができません。

1. 逐次実行で重複レコードを登録する

コンソールプログラムを作成し、必要なDLLとターゲットの.NET Framework を .NET Framework 4.0 にします。詳細な手順は[Dynamics CRM 2011]遅延バインディング(Late Binding) でCRM Webサービス に接続する を参照。

検証プログラムは次の通りです。下記サンプルでは、並列で実行する処理(ParallelRegistContact)はコメントにしいます。SequentialRegistContact は逐次的にメールアドレスが同じ取引先担当者レコードを登録するメソッドです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk.Client;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;

namespace AddDuplicateRecord
{
    class Program
    {
        static void Main(string[] args)
        {
            DuplicateContact duplicate = new DuplicateContact();
            duplicate.SequentialRegistContact("duplicate@test.jp");
            //duplicate.ParallelRegistContact("parallel@test.jp");

            Console.WriteLine("Press Enter.");
            Console.ReadLine();
        }
    }

    class DuplicateContact
    {
        public void ParallelRegistContact(string email)
        {
            IList<Entity> contacts = new List<Entity>();
            contacts.Add(CreateContact("First1", "Last1", email));
            contacts.Add(CreateContact("First2", "Last2", email));
            contacts.Add(CreateContact("First3", "Last3", email));

            var parallelTask = contacts.AsParallel();
            parallelTask.ForAll(entity =>
            {
                Console.WriteLine(entity["firstname"]);
                using (OrganizationServiceProxy service = GetOrganizationService("crmsvr01", "org01"))
                {
                    try
                    {
                        // 連続して同じメールアドレスのコンタクトを作成する
                        CreateRequest request = new CreateRequest();
                        request.Parameters["SuppressDuplicateDetection"] = false; // 重複レコード登録禁止
                        request.Target = entity;
                        service.Execute(request);
                    }
                    catch (TimeoutException)
                    {
                        Console.WriteLine("Timeout");
                    }
                    catch (FaultException<OrganizationServiceFault> fe)
                    {
                        Console.WriteLine("FaultException<OrganizationServiceFault>");
                        Console.WriteLine(fe.Detail.Message);
                    }
                    catch (FaultException)
                    {
                        Console.WriteLine("FaultException");
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Exception");
                        Console.WriteLine(ex.Message);
                    }
                    Console.WriteLine(entity["firstname"]);
                }
            });
        }
        public void SequentialRegistContact(string email)
        {
            using (OrganizationServiceProxy service = GetOrganizationService("crmsvr01", "org01"))
            {
                try
                {
                    // 連続して同じメールアドレスのコンタクトを作成する
                    CreateRequest request = new CreateRequest();
                    request.Parameters["SuppressDuplicateDetection"] = false; // 重複レコード登録禁止
                    request.Target =CreateContact("First1", "Last1", email);
                    service.Execute(request);

                    request.Target = CreateContact("First2", "Last2", email);
                    service.Execute(request);
                }
                catch (TimeoutException)
                {
                    Console.WriteLine("Timeout");
                }
                catch (FaultException<OrganizationServiceFault> fe)
                {
                    Console.WriteLine("FaultException<OrganizationServiceFault>");
                    Console.WriteLine(fe.Detail.Message);
                }
                catch (FaultException)
                {
                    Console.WriteLine("FaultException");
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception");
                    Console.WriteLine(ex.Message);
                }
            }
        }
        private Entity CreateContact(string firstname, string lastname, string email)
        {
            Entity contact = new Entity("contact");
            contact.Attributes["firstname"] = firstname;
            contact.Attributes["lastname"] = lastname;
            contact.Attributes["emailaddress1"] = email;

            return contact;
        }
        /// <summary>  
        /// Crm On-Premise に接続する  
        /// </summary>  
        /// <param name="server">サーバー名</param>  
        /// <param name="orgname">組織名</param>  
        /// <returns>OrganizationServiceProxy のインスタンス</returns>  
        private OrganizationServiceProxy GetOrganizationService(string server, string orgname)
        {
            string endpointaddress = string.Format("http://{0}/{1}/XRMServices/2011/Organization.svc", server, orgname);
            OrganizationServiceProxy organizationService = new OrganizationServiceProxy(new Uri(endpointaddress), null,
                new System.ServiceModel.Description.ClientCredentials(), null);

            return organizationService;
        } 
    }
}

プログラムを実行すると次のエラーが発生します。CRMの画面を確認すると1レコードのみが登録されていることを確認できます。

FaultException A record was not created or updated because a duplicate of the current record already exists. 

ここで、注意ですが、レコード作成時に重複レコードを登録しないようにするには、 IOrganizationService.Execute メソッドを使用します。OrganizationRequestのParametersのSuppressDuplicateDetectionをfalseにしてください。パラメータを指定できない、IOrganizationService.Create を使用すると重複ルールに一致するレコードが登録されてしまいます。

2 並列実行により重複するレコードを登録する

サンプルプログラムのMainメソッドでコメントアウトされていたParallelRegistContactを呼び出す処理のコメントを解除します。すぐ上の、SequentialRegistContact を呼び出している分をコメントアウトします。

プログラムを実行すると重複ルールに一致するレコードが複数登録できてしまいます。CRMの画面でも重複したレコードを登録できたことを確認できます。

記事冒頭でも記載しましたが、PLINQを使用し、並列して重複ルールに一致するレコードを登録しています。そのため、記載したプログラムを使用して重複するレコードが登録できることを検証する場合、コア数が2以上の環境でプログラムを実行してください。

3. まとめ

WebクライアントやOutlook クライアントで登録したレコードのマッチコードが同期的に作成されるようになったことで、重複ルールに一致するレコードがほぼリアルタイムで検出されるようになり利便性が向上しました。4.0時代は重複レコードの検出が行われるようになるまで5分ほど待つ必要があったので、非常にありがたい修正点です。

検証プログラムのように、ほとんど同じタイミングで重複ルールに該当するレコードを登録した場合、レコードが登録できてしまうことも確認しました。これは Dynamics CRM 4.0 のときも同じように登録できたため予想通りの結果でした。