Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
みなさん、こんにちは。
今日は開発者向けの情報として、個人グラフのエクスポートと
インポートを考察した結果とサンプルコードを公開します。尚
対象は 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 を利用する必要があります。個人ビューよりは考慮事項が少ない
ですが、事前にカスタマイズは新しい環境に適用済である必要が
あります。
- 中村 憲一郎