Dynamics CRM 個人ビューのエクスポートとインポート その 1

※2015/7/21 ユーザービュー取得時の実行ユーザー偽装コードを追加しました。

みなさん、こんにちは。

今日は開発者向けの情報として、個人ビューのエクスポートと
インポートを考察した結果とサンプルコードを公開します。尚
対象は Microsoft Dynamics CRM 2013 および同等バージョンの
Microsoft Dynamics CRM Online とします。

今回はまず個人ビューのエクスポートを考えます。
※以下で紹介する内容はあくまで個人の見解であり、コードは
自己責任で検証を行ってください。

概要:個人ビュー移行の問題

テスト環境から UAT や本番環境にソリューションを移行する場合
通常はソリューションのエクスポートとインポートでカスタマイズ
を移行し、データのエクスポートとインポートでデータを移行する
事になりますが、よく課題となるのが「個人ビュー」です。

個人ビューはソリューションで移行が行えず、またデータ移行も
行えないため、通常は手動で再作成となりますが、ユーザー数や
ビューの数から現実的でない場合があります。そこで今回は
SDK を利用して個人ビューの移動が行えるか検討してみます。

個人ビューエンティティ

個人ビューは UserQuery エンティティ に格納されています。メタ
データを確認すると、以下フィールドに重要な情報が格納されて
いることが分かります。

ColumnSetXml : ビューで利用される列の情報
FetchXml:ビューで利用されるクエリの情報
LayoutXml:ビューで利用されるレイアウト情報
OwnerId:ビューの所有者
QueryType:ビューのタイプ
ReturnedTypeCode:ビューの対象エンティティ

ビューのタイプの関する情報はこちら

環境によって値が変わる要素は OwnerId と ReturnedTypeCode
(カスタムエンティティ) であると想定します。またエンティティ
用の個人ビューは QueryType =  0 であると仮置きします。

データの取得

では早速 SDK でデータを取得してみます。以下は QueryExpression
を利用したクエリの例です。

// QueryExpression の生成
QueryExpression query = new QueryExpression();
// エンティティ名を指定
query.EntityName = UserQuery.EntityLogicalName;
// すべての列を取得
query.ColumnSet = new ColumnSet(true);
// 条件を作成
query.Criteria = new FilterExpression();
// 条件として QueryType = 0 を指定
query.Criteria.AddCondition(new ConditionExpression("querytype", ConditionOperator.Equal, 0));
// 結果を取得
var results = _serviceProxy.RetrieveMultiple(query); 

上記クエリで取得した結果の 1 件以下に表示します。

image

まず気が付くのは、ReturnedTypeCode が数値ではなく、
エンティティの論理名として返ってきています。また
OwnerId は Guid ではなく、EntityReference です。

次にカスタムエンティティの個人ビューを以下に示します。

image

やはり ReturnedTypeCode はエンティティの論理名です。しかし
その一方で LayoutXml には object=”10005” という文字列が見える
ため、エンティティの ObjectTypeCode を含んでいる列がある事も
見て取れます。

データの保存

データの取得が行えることはわかったので、後でインポートをする
ためにデータをディスクに保存してみます。また後から情報も紐付
を変更できるよう、ユーザー単位で個人ビューを取得して、ビュー
の所有者を特定する一意の値として DomainName 列を利用します。

作成したコードは以下の通りです。これでプログラムの実行フォルダ
に「UserQuery_ドメイン名.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 = UserQuery.EntityLogicalName;
    // すべての列を取得
    query.ColumnSet = new ColumnSet(true);
    // 条件を作成
    query.Criteria = new FilterExpression();
    // 条件として QueryType = 0 を指定
    query.Criteria.AddCondition(new ConditionExpression("querytype", ConditionOperator.Equal, 0));
    // 条件として所有者を指定
    query.Criteria.AddCondition(new ConditionExpression("ownerid", ConditionOperator.Equal, user.Id));
    // 実行ユーザーを偽装 ※2015/07/21 追加
    _serviceProxy.CallerId = user.Id;
    // 結果を取得
    EntityCollection views = _serviceProxy.RetrieveMultiple(query);

    // ビューが 1 件もない場合は次のユーザーの処理
    if (views.Entities.Count == 0)
        continue;
    // シリアライザーを作成
    DataContractSerializer serializer = new DataContractSerializer(typeof(EntityCollection));

    // データの書き出し
    using (MemoryStream ms = new MemoryStream())
    {
        // データをシリアライズ
        serializer.WriteObject(ms, views);
        // シリアライズした結果を文字列に変換
        var xml = Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length);
        // DomainName を利用してファイルを作成し保存
        StreamWriter sw = new StreamWriter("UserQuery_" + user.DomainName + ".xml");
        sw.Write(xml);
        sw.Close();
    }
}

保存されたファイルを XML エディターで開いて確認してください。

次回は保存したファイルを再度オブジェクト化して、新しい環境に
インポートする考察を行います。

- 中村 憲一郎