Dynamics CRM 個人グラフのエクスポートとインポート
みなさん、こんにちは。
今日は開発者向けの情報として、個人グラフのエクスポートと
インポートを考察した結果とサンプルコードを公開します。尚
対象は Microsoft Dynamics CRM 2013 および同等バージョンの
Microsoft Dynamics CRM Online とします。
以前個人ビューの移行についての考察を紹介した際は、2 回に
分けてインポートとエクスポートを紹介しましたが、今回は
1 回で紹介してしまいます。
Dynamics CRM 個人ビューのエクスポートとインポート その 1
Dynamics CRM 個人ビューのエクスポートとインポート その 2
※以下で紹介する内容はあくまで個人の見解であり、コードは
自己責任で検証を行ってください。
概要:個人グラフ移行の問題
個人グラフを別組織に移動する際の課題は、個人ビューの移動と
よく似ていますが、個人グラフは手動でエクスポート/インポート
が行えます。ただし各グラフの所有者でログインして作業する必要
があるためユーザーの協力が必要です。
個人グラフエンティティ
個人グラフは UserQueryVisualization エンティティ に格納されて
います。メタデータを確認すると、以下フィールドに重要な情報が
格納されていることが分かります。
datadescription : グラフで利用するデータのクエリ部分
presentationdescription : グラフを表現する部分
primaryentitytypecode : 対象エンティティ
データの取得
では早速 SDK でデータを取得してみます。以下は QueryExpression
を利用したクエリの例です。
// 取得したレコードを SystemUser に変換
SystemUser user = result.ToEntity<SystemUser>();
// QueryExpression の生成
QueryExpression query = new QueryExpression();
// エンティティ名を指定
query.EntityName = UserQueryVisualization.EntityLogicalName;
// すべての列を取得
query.ColumnSet = new ColumnSet(true);
// 結果を取得
EntityCollection charts = _serviceProxy.RetrieveMultiple(query);
上記クエリで取得した結果の 1 件以下に表示します。
個人ビューと同じく OwnerId は Guid ではなく EntityReference で
あること、また PrimaryEntityTypeCode は ObjectTypeCode ではなく、
エンティティの論理名です。
データの保存
次に、後でインポートをするためにデータをディスクに保存します。
また後から情報も紐付を変更できるよう、ユーザー単位で個人グラフ
を取得して、グラフの所有者を特定する一意の値として DomainName
列を利用します。
作成したコードは以下の通りです。これでプログラムの実行フォルダ
に「UserChart_ドメイン名.xml」のファイルが作成されます。
// 有効なユーザーを取得
// FetchXML は高度な検索より取得
EntityCollection resutls = _serviceProxy.RetrieveMultiple(new FetchExpression(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
<entity name='systemuser'>
<attribute name='domainname' />
<filter type='and'>
<condition attribute='isdisabled' operator='eq' value='0' />
<condition attribute='accessmode' operator='ne' value='3' />
</filter>
</entity>
</fetch>"));
// ユーザーごとに個人グラフを取得
foreach (var result in resutls.Entities)
{
// 取得したレコードを SystemUser に変換
SystemUser user = result.ToEntity<SystemUser>();
// QueryExpression の生成
QueryExpression query = new QueryExpression();
// エンティティ名を指定
query.EntityName = UserQueryVisualization.EntityLogicalName;
// すべての列を取得
query.ColumnSet = new ColumnSet(true);
// 条件を作成
query.Criteria = new FilterExpression();
query.Criteria.AddCondition(new ConditionExpression("ownerid", ConditionOperator.Equal, user.Id));
// 結果を取得
EntityCollection charts= _serviceProxy.RetrieveMultiple(query);
if (charts.Entities.Count == 0)
continue;
// シリアライザーを作成
DataContractSerializer serializer = new DataContractSerializer(typeof(EntityCollection));
// データの書き出し
using (MemoryStream ms = new MemoryStream())
{
// データをシリアライズ
serializer.WriteObject(ms, charts);
// シリアライズした結果を文字列に変換
var xml = Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length);
// DomainName を利用してファイルを作成し保存
StreamWriter sw = new StreamWriter("UserChart_" + user.DomainName + ".xml");
sw.Write(xml);
sw.Close();
}
}
エクスポートは以上で完了です。次にインポートを考えてみます。
ファイルとユーザーの取得
まずは上記でディスクにシリアライズして保存したデータをデシリア
ライズして読み込むところから検討してみます。まず保存した XML
ファイルを以下のコードで取得しました。
// フォルダ内で UserChart で始まる XML ファイルを全て取得
string[] files = System.IO.Directory.GetFiles(".", "UserChart_*xml",
System.IO.SearchOption.AllDirectories);
上記ではプログラムの実行フォルダに XML ファイルが存在する
想定となっています。取得したファイルはユーザー単位となって
いるため、ファイルを 1 つづつ処理します。またファイル処理時
に、新しい環境のユーザー情報を取得する必要があります。
// それぞれのファイルごとに処理
foreach(string file in files)
{
// ユーザーの特定
string domainname = file.Substring(file.IndexOf('_') + 1, file.IndexOf(".xml") - file.IndexOf('_') - 1);
// FetchXML を利用してユーザーを取得
SystemUser user = (SystemUser)_serviceProxy.RetrieveMultiple(new FetchExpression(
String.Format(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
<entity name='systemuser'>
<attribute name='fullname' />
<filter type='and'>
<condition attribute='domainname' operator='eq' value='{0}' />
</filter>
</entity>
</fetch>", domainname))).Entities.FirstOrDefault();
// もしユーザーがいない場合は次のファイルを処理
if (user == null)
continue;
// ここで個人グラフ復元処理
}
個人グラフの復元
新しい環境のユーザーが特定できた時点で、データの復元を検討
していきます。
// ファイルから UserQueryVisualization をデシリアライズ
DataContractSerializer serializer = new DataContractSerializer(typeof(EntityCollection));
XmlReader reader = XmlReader.Create(file);
//XMLファイルから読み込み、デシリアライズする
EntityCollection charts = (EntityCollection)serializer.ReadObject(reader);
//ファイルを閉じる
reader.Close();
// 取得した UserQueryVisualization を 1 件ずつ処理
foreach(var chart in charts.Entities)
{
// ここで個別の個人グラフを処理
}
これで chart ローカル変数にデータを復元できました。次に
個人グラフを 1 件づつ復元していきますが、まずは所有者の
情報を新しいものに変更します。
// 取得したデータを UserQueryVisualization 型に変換
UserQueryVisualization userQueryVisualization = chart.ToEntity<UserQueryVisualization>();
// 所有者を設定
userQueryVisualization.OwnerId = user.ToEntityReference();
最後に個人グラフを作成します。
// 個人グラフの作成
_serviceProxy.Create(userQueryVisualization);
その他の考察
今回のコード内での懸念点を以下の通りです。
- ユーザーの特定を DomainName で行えるか。たとえばオンライン
環境のようにドメインが変わる場合は他のフィールドで特定が必要。
- 個人グラフ作成時に GUID が指定されている。ベストプラクティス
では Microsoft Dynamics CRM サーバーに Guid の付与は任せるべき
であるためデシリアライズしたものをそのまま利用せず、新しい
インスタンスに各種値だけ渡して処理すべきか。
- 既に同じ名前のグラフが存在する場合の重複処理、また各種処理
で失敗した場合のハンドリングを考えていない。等。
EnableProxyTypes
EnableProxyTypes メソッドを組織プロキシに対して実行することで
事前バインドを利用できるようになりますが、今回の処理では以下
の点ご注意ください。
- データのエクスポート処理では EnableProxyTypes は使わない。
これはシリアライズする際に型情報が不足するためです。
- データのインポート処理では EnableProxyTypes を使う。
これは上記コードで事前バインドを使っているためです。
まとめ
個人グラフは UI を利用したエクスポート、インポートが行えますが
ユーザーごとに処理が必要となるため、一括で移動する場合には、
SDK を利用する必要があります。個人ビューよりは考慮事項が少ない
ですが、事前にカスタマイズは新しい環境に適用済である必要が
あります。
- 中村 憲一郎