【Dynamics CRM 2011】RESTを使ってデータを取得する
Dynamics CRMにはRESTエンドポイントが用意されています。ODataと呼ばれるプロトコルを使うことで、データの抽出や並べ替え等を行うことも出来ます。
正直な話、SDKの完全下位互換ですし、多少細かいことをしようと思ったらSOAPエンドポイントを使うのも手ですし、もしDynamics CRMが使用しているSQL Serverに接続できる環境であるならFiltered Viewを使うべきでしょう。(ただし、Filtered Viewは抽出のみ)
SDKが使えない(C#やJScriptが使えない)環境だが、Dynamicsに対しHTTP接続は可能であり、なおかつデータの更新や作成を行うクライアントアプリケーションを作成したいと言う、現状かなり限定的な用途でしか使う意味がない機能です。
RESTエンドポイントに接続する
リファレンスに細かく書いてあるので、要点だけ。
ベースとなるURIは以下の通りです。
[Your Organization Root URL]/XRMServices/2011/OrganizationData.svc
[Your Organization Root URL]と書いてある通り、きちんと組織名まで入れてあげましょう。このベースURIに「/取得したいエンティティ名+Set」を加えます。例としてContactでも表示してみましょう。
[Your Organization Root URL]/XRMServices/2011/OrganizationData.svc/ContactSet?
エンティティ名はLogical NameではなくSchema Nameです。また、大文字小文字の判定をしているので、正確に入力しましょう。例えば「contactset」のように指定すると400が返って来ます。
とりあえずこの段階でも適当なブラウザで投げれば名前の一覧が返って来ます。所詮はGETメソッドなので…。
一覧が返ってくるのを確認したら、次はODataによるクエリを作成しましょう。
RESTで使用するクエリを作成する
Dynamic CRM 2011の時点ではODataの全てのクエリを使用することは出来ません。(2013は未確認)使えるクエリの一覧が記載されているリファレンスがあるので、それを参考に作っていきましょう。
基本的にはリファレンスを読んでそのまま作ればOKです。注意する点はやはりLogical NameではなくSchema Nameを指定することと、$filterで指定する値が文字列の場合はシングルクオートでくくってやることぐらいです。
Like検索にあたるものはstartswith、substringof、endswithで可能です。何でsubstringofと言う名前なのかはわかりませんが、要はcontainsです。starts/endswithとsubstringofは引数の順番が逆なので(なんだこの設計…)、引っかからないようにしましょう。
また、マルチバイト文字はちゃんとエンコードしてあげなくてはなりません。また、エンコードする箇所はマルチバイト文字のみです。
$expandに指定する項目はフィールドではなく関連付けのスキーマ名です。このあたりの例を見る限りだと、「指定した関連/指定した関連と紐付くエンティティのフィールド」と言う形で値を取れるみたいです。
$expandすれば$filterでも使えそうなことが書いてありますが、1:Nの関係だとダメでした(N:1だと可能)。謎です。
補足するとすればこんなところでしょうか。実際にクエリを作ってみましょう。
[Your Organization Root URL]/XRMServices/2011/OrganizationData.svc/ContactSet?$select=FullName,CreatedOn&$filter=startswith(FullName,'hoge')
複数の演算子を使う場合は&でつなげてあげればOKです。selectやexpandで複数指定する場合は,で区切りましょう。SQLと大体一緒です。
[2014/03/27追記]
自分でやっててちょっとハマったので追記。
ワークフローをRESTで取得できないかなと思い、こんなURIを作りました。
[Your Organization Root URL]/XRMServices/2011/OrganizationData.svc/WorkflowSet?$filter=Type eq 1
これだと正しくデータが返ってきません。Workflow.Typeはオプションセット型であり、System.Int32と互換性がないからです。
オプションセットの値を$filterに適用したい場合はこうします。
[Your Organization Root URL]/XRMServices/2011/OrganizationData.svc/WorkflowSet?$filter=Type/Value eq 1
[2014/03/27追記ここまで]
データを解析する
HTTPリクエストのヘッダに色々指定することで返ってくる形式をjsonかxmlの二つから選ぶことが出来ます。デフォルトだとxmlです。どっちも地獄みたいな形式のデータが返ってくるので、好きな方/得意な方の地獄を選んでください。
jsonで欲しい場合は
Accept:appication/json Content-Type:application/json
としてあげればOKです。jsonにしろxmlにしろ、Content-Typeにcharset=utf-8を指定するのを忘れないようにしましょう。
また、「jQueryでjsonをゴニョゴニョしたい」とか「こんなのレスポンスクソすぎてAjax使った非同期処理じゃないとやってられないよ」って人は、幸運にもいつものように大して使えないサンプルがあるので参考にしてみてください。
ただ、解析の手間や学習コストetcを考えると、RESTを使うぐらいならJScript用のSdkを使うほうがいいと思います。超簡単なクエリを飛ばすぐらいならRESTでもいいと思いますが、あんまりがっつりとしたものは作るのが大変です。
それじゃあ早速返ってきたデータを見てみましょう。なるべく色んなサンプルが欲しいので、なるべく色んな型をとってくるクエリを投げてみました。
まずはxmlです。
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <feed xml:base="http://foo/bar/xrmservices/2011/OrganizationData.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom"> <title type="text">ContactSet</title> <id>http://foo/bar/XRMServices/2011/OrganizationData.svc/ContactSet</id> <updated>2013-12-16T02:21:35Z</updated> <link rel="self" title="ContactSet" href="ContactSet" /> <entry> <id>http://foo/bar/xrmservices/2011/OrganizationData.svc/ContactSet(guid'89dadf44-b95f-e211-80b2-00155dd78608')</id> <title type="text">hoge piyo</title> <updated>2013-12-16T02:21:35Z</updated> <author> <name /> </author> <link rel="edit" title="Contact" href="ContactSet(guid'89dadf44-b95f-e211-80b2-00155dd78608')" /> <category term="Microsoft.Crm.Sdk.Data.Services.Contact" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> <content type="application/xml"> <m:properties> <d:AnnualIncome m:type="Microsoft.Crm.Sdk.Data.Services.Money"> <d:Value m:type="Edm.Decimal" m:null="true" /> </d:AnnualIncome> <d:CreatedOn m:type="Edm.DateTime">2013-01-16T08:46:51Z</d:CreatedOn> <d:VersionNumber m:type="Edm.Int64">44068435</d:VersionNumber> <d:BirthDate m:type="Edm.DateTime">1985-01-31T15:00:00Z</d:BirthDate> <d:Address1_Latitude m:type="Edm.Double" m:null="true" /> <d:NumberOfChildren m:type="Edm.Int32" m:null="true" /> <d:NickName m:null="true" /> <d:Merged m:type="Edm.Boolean">false</d:Merged> <d:ContactId m:type="Edm.Guid">89dadf44-b95f-e211-80b2-00155dd78608</d:ContactId> <d:ExchangeRate m:type="Edm.Decimal">1.0000000000</d:ExchangeRate> <d:StateCode m:type="Microsoft.Crm.Sdk.Data.Services.OptionSetValue"> <d:Value m:type="Edm.Int32">0</d:Value> </d:StateCode> <d:StatusCode m:type="Microsoft.Crm.Sdk.Data.Services.OptionSetValue"> <d:Value m:type="Edm.Int32">1</d:Value> </d:StatusCode> <d:Description m:null="true" /> <d:FullName>hoge fuga</d:FullName> <d:CreatedBy m:type="Microsoft.Crm.Sdk.Data.Services.EntityReference"> <d:Id m:type="Edm.Guid">1dc06d24-8437-e211-82c5-00155dd78608</d:Id> <d:LogicalName>systemuser</d:LogicalName> <d:Name>piyo piyo</d:Name> </d:CreatedBy> </m:properties> </content> </entry> <link rel="next" href="http://foo/bar/XRMServices/2011/OrganizationData.svc/ContactSet?$select=VersionNumber,Merged,BirthDate,CreatedOn,ExchangeRate,Address1_Latitude,NumberOfChildren,CreatedBy,Description,AnnualIncome,GenderCode,StateCode,StatusCode,FullName,NickName,ContactId&$skiptoken=1,'contactid','%7B286F25FD-DD66-E211-80B2-00155DD78608%7D','%7B89DADF44-B95F-E211-80B2-00155DD78608%7D'" /> </feed>
中々ひどいですね。ちょっと構造を整理してみましょう。
feed ├title ├id ├updated ├link ├entry │├id │├title │├updated │├author ││└name │├link │├category │└content │ └m:properties │ ├d:フィールド名 │ ├... │ └d:フィールド名 └link
こんなところでしょうか。
よく読んでみると、欲しいものはどうやらfeed->entry->content->m:properties->d:フィールド名のところにいるみたいです。今回は一件だけですが、複数件取得できる場合はentryが複数生成されます。
nullの場合はd:フィールド名のAttributeとしてm:null=”true”が指定されてるみたいです。nullじゃないときはm:null=”false”とはなりません。本当にパースされること前提で作られてるんでしょうか?こんなことばっかりやってるからxmlは廃れたんですよ?
ひどいことにMoney型、OptionSetValue型、EntityReference型の場合は更に深いところにいます。
└m:properties ├d:フィールド名 m:type="Microsoft.Crm.Sdk.Data.Services.Money" │└d:Value ├d:フィールド名 m:type="Microsoft.Crm.Sdk.Data.Services.OptionSetValue" │└d:Value └d:フィールド名 m:type="Microsoft.Crm.Sdk.Data.Services.EntityReference" ├d:Id ├d:LogicalName └d:Name
nullチェックをする場合はd:Valueのところにあるm:null=”true”を見なくてはいけません。ふざけてるんですかね。EntityReferenceの場合はどうなんでしょう。多分Idのとこにいると思います。(投げやり)
気を取り直してjsonを見てみましょう。
{ "d" : { "results": [ { "__metadata": { "uri": "http://foo/bar/xrmservices/2011/OrganizationData.svc/ContactSet(guid'89dadf44-b95f-e211-80b2-00155dd78608')", "type": "Microsoft.Crm.Sdk.Data.Services.Contact" }, "AnnualIncome": { "__metadata": { "type": "Microsoft.Crm.Sdk.Data.Services.Money" }, "Value": null }, "CreatedOn": "\/Date(1358326011000)\/", "VersionNumber": "44068435", "BirthDate": "\/Date(476031600000)\/", "Address1_Latitude": null, "NumberOfChildren": null, "NickName": null, "Merged": false, "ContactId": "89dadf44-b95f-e211-80b2-00155dd78608", "ExchangeRate": "1.0000000000", "StateCode": { "__metadata": { "type": "Microsoft.Crm.Sdk.Data.Services.OptionSetValue" }, "Value": 0 }, "StatusCode": { "__metadata": { "type": "Microsoft.Crm.Sdk.Data.Services.OptionSetValue" }, "Value": 1 }, "Description": null, "FullName": "hoge fuga", "CreatedBy": { "__metadata": { "type": "Microsoft.Crm.Sdk.Data.Services.EntityReference" }, "Id": "1dc06d24-8437-e211-82c5-00155dd78608", "LogicalName": "systemuser", "Name": "piyo piyo" } } ], "__next": "http://foo/bar/XRMServices/2011/OrganizationData.svc/ContactSet?$select=VersionNumber,Merged,BirthDate,CreatedOn,ExchangeRate,Address1_Latitude,NumberOfChildren,CreatedBy,Description,AnnualIncome,StateCode,StatusCode,FullName,NickName,ContactId&$skiptoken=1,'contactid','%7B286F25FD-DD66-E211-80B2-00155DD78608%7D','%7B89DADF44-B95F-E211-80B2-00155DD78608%7D'" } }
全然こっちの方がマシ…と言うかxmlが何だったのかってぐらい必要最低限のものしかないですね。
これも一応構造を整理しましょう。
(root) └d ├results │├__metadata ││├uri ││└type │├フィールド名 │├... │└フィールド名 └next
普通にわかりやすいですね。複数件ある場合はresultsの配列の中身が増えていきます。
そしてやはりMoney型、OptionSetValue型、EntityReference型の場合は更に一階層下に値があります。
└results ├フィールド名 │├__metadata ││└type(Microsoft.Crm.Sdk.Data.Services.Money) │└Value ├フィールド名 │├__metadata ││└type(Microsoft.Crm.Sdk.Data.Services.OptionSetValue) │└Value └フィールド名 ├__metadata │└type(Microsoft.Crm.Sdk.Data.Services.EntityReference) ├Id ├LogicalName └Name
nullの場合は値にnullが入っています。すばらしい。すばらしいって言うか、当たり前では?
expandした場合はこうなります。まずはxml。
feed ├title ├id ├updated ├link ├entry │├id │├title │├updated │├author ││└name │├link │├link(rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/関連付け名") ││└m:inline ││ └entry ││ ├id ││ ├title ││ ├updated ││ ├author ││ │└name ││ ├link ││ ├category ││ └content ││ └m:properties ││ ├d:フィールド名 ││ ├... ││ └d:フィールド名 │├category │└content │ └m:properties │ ├d:フィールド名 │ ├... │ └d:フィールド名 └link
feed->entry->link(relに関連付け名を含むURI)->m:inlineの中にentryが生まれるイメージです。また深いですね。
jsonはこうです。
(root) └d ├results │├__metadata ││├uri ││└type │├フィールド名 │├... │└関連付け名 │ ├__metadata │ │├uri │ │└type │ ├フィールド名 │ ├... │ └フィールド名 └next
関連付け名の配列の中にresultsが生まれるイメージです。
続きのデータを取得する
今回は複数件あるものを無理矢理一件に削ったので、link rel=”next”/nextがあります。ここのURIを使用することで、続きのデータを取得することが出来ます。
デフォルトでは50件までしか取れません。もっと返して欲しい場合はサーバの設定をいじってやる必要があります。
また、$topを使用することで取得件数に制限をかけることもできます。
まとめ
中々しんどいですが、こんな感じにある程度どんな環境でもDynamics CRMのデータを取得することができます。当然VBAでも。
とは言え、メタデータに関する知識なども必要になってくるので、微妙に敷居は高いと思います。あんまりオススメはしません。