Partager via


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 件以下に表示します。

image

個人ビューと同じく 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 を利用する必要があります。個人ビューよりは考慮事項が少ない
ですが、事前にカスタマイズは新しい環境に適用済である必要が
あります。

- 中村 憲一郎