Dynamics CRM 2011 では、 事前バインディングにOrganizationServiceContext(または派生クラス)を使用します。4.0の時代は、Crm WebサービスのWSDLから作成するプロキシを使用しました。今回は crmsvcutil を使用せずに、OrganizationServiceContext を使用してエンティティレコードに対するCRUD操作を行うサンプルを掲載します。

OrganizationServiceContext を使用する場合は、WCF Data Service と同じように簡単にエンティティレコードの作成、更新、削除、検索、関連付けなどの操作を実装できます。ただし、IOrganizationService.Execute メソッドを使用する必要のあるメッセージは、OrganizationServiceContext.Execute メソッドを使用する必要があります。多対多(N:N)の関連付けをする場合は IOrganizationService の Associate, Disassociate メソッドもしくは AssociateRequest, DisassociateRequest を使用する必要があります。

動作確認用の環境

  • Windows Server 2008 R2 上に構築してオールインワン環境 Dynamics CRM 2011 UR 3
  • Visual Studio 2010 Professional

本記事で使用しているOrganizationServiceContextのメソッドに関しては、組織サービス コンテキスト クラスの使用を参照してください。

1. サンプルプロジェクトの作成

Visual Studio 2010 を起動します。コンソールアプリケーション用のプロジェクトを作成します。プロジェクトに必要なdllの参照を追加します。 microsoft.xrm.sdk.dll, microsoft.crm.sdk.proxy.dll, System.ServiceModel.dll, System.Runtime.Serialization.dll の参照を設定します。詳細は、[Dynamics CRM 2011]遅延バインディング(Late Binding) でCRM Webサービス に接続する を参照。

既定で作成される Program.cs に次のようのコードを実装します。組織サービス コンテキスト クラスの使用で記載のあるOrganizationServiceContext のメソッドはなるべく使うようにしました。一部AttachLink などは使っていませんが、下記サンプルはSDKのヘルプで使い方はわかると思います。

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

namespace EarlyBindingSample01
{
    class Program
    {
        /// <summary>
        /// メインメソッド
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            using (OrganizationServiceProxy service = GetOrganizationService("crmsvr01", "org01"))
            {
                // 次のステートメントは、事前バインド型をサポートするために必要な処理です
                service.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());

                OrganizationServiceContext context = new OrganizationServiceContext(service);

                // AddObject
                Entity contact = new Entity("contact");
                contact["firstname"] = "taro";
                contact["lastname"] = "test";

                context.AddObject(contact);

                // SaveChanges メソッドの既定の振る舞いを変更したい場合はSaveChangesDefaultOptions を指定
                //context.SaveChangesDefaultOptions = SaveChangesOptions.ContinueOnError;
                context.SaveChanges();

                // 1:Nの設定(Add-Link, AddRelatedObject)
                // すでに関連のあるレコード間でコンテキスト上で関連を設定する場合はAttachLinkを使用します
                context.Attach(contact);
                // 所属取引先企業/上司
                Entity account = new Entity("account");
                account["name"] = "sample company";
                context.AddObject(account);
                context.AddLink(account, new Relationship("contact_customer_accounts"), contact);

                // 取引先責任者設定
                // AddRelatedObjectを使用すると、AddObjectとAddLinkを呼び出すのと同じ処理がされる
                Entity account2 = new Entity("account");
                account2["name"] = "sample company 2";
                context.AddRelatedObject(contact, new Relationship("account_primary_contact"), account2);
                
                context.SaveChanges();


                // LINQ クエリを使用した検索と更新 UpdateObject
                var query = from e in context.CreateQuery("contact")
                            where e.GetAttributeValue<string>("firstname") == "taro"
                            select e;

                foreach (var item in query)
                {
                    item["lastname"] = "last001";
                    context.UpdateObject(item);

                    // 関連する所属取引先企業/上司をロード,属性を遅延ロードすることもできる
                    if (item.Attributes.ContainsKey("parentcustomerid"))
                    {
                        context.LoadProperty(item, new Relationship("contact_customer_accounts"));
                        // ロードした関連する取引先企業を取得
                        EntityCollection cols = item.RelatedEntities[new Relationship("contact_customer_accounts")];
                        Console.WriteLine("Parent Account Id:{0}", cols[0].Attributes["accountid"]);
                    }
                }

                context.SaveChanges();

                // レコードを削除する DeleteObject
                query = from e in context.CreateQuery("contact")
                            where e.GetAttributeValue<string>("firstname") == "taro"
                            select e;
                query.ToList().ForEach(x => context.DeleteObject(x));
                context.SaveChanges();
            }
        }
        /// <summary>  
        /// Crm On-Premise に接続する  
        /// </summary>  
        /// <param name="server">サーバー名</param>  
        /// <param name="orgname">組織名</param>  
        /// <returns>OrganizationServiceProxy のインスタンス</returns>  
        public static 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;
        } 

    }
}

LoadProperty は必要になった時点でエンティティの属性や関連エンティティを遅延ロードすることができて便利です。読み込んだ関連エンティティは上記サンプルのように、Entity.RelatedEntities を使用して取得することができます。 

2. OrganizationServiceContext を使用した場合の重複データの検出

OrganizationServiceContext を使用して、レコードの作成や更新を行う場合は、重複レコードの検出が行われません。重複レコードを登録してもそのまま登録されます。重複レコードの検出をする場合は、SuppressDuplicateDetection を false にする必要があります。重複レコードの検出は[Dynamics CRM 2011]Webクライアントでも重複チェック用のマッチコード作成が同期的に行われるようになりました。でも本気でやると重複レコード登録できますというお話 を参照。

3. まとめ

今回の説明は以上です。指摘点などがあればご連絡ください。OrganizationServiceContext を使用すると、 Entity Frameowrk や WCF Data Service と同じプログラミングモデルで CRUD操作の実行や LINQを使用することができます。ただし、IOrganizationService.Execute で実行できるすべての処理を行えるわけではないので注意が必要です。Execute メソッドで行うリクエストが必要な場合は、OrganizationServiceContext.Execute メソッドを使用します。