Dynamics CRM 個人ビューのエクスポートとインポート その 2
みなさん、こんにちは。
前回に続き、開発者向けの情報として、個人ビューのエクスポート
とインポートを考察した結果とサンプルコードを公開します。尚
対象は Microsoft Dynamics CRM 2013 および同等バージョンの
Microsoft Dynamics CRM Online とします。
今回は、前回エクスポートした個人ビューのインポートを考えます。
※以下で紹介する内容はあくまで個人の見解であり、コードは
自己責任で検証を行ってください。
ファイルとユーザーの取得
まずは前回ディスクにシリアライズして保存したデータをデシリア
ライズして読み込むところから検討してみます。まず保存した XML
ファイルを以下のコードで取得しました。
// フォルダ内で UserQuery で始まる XML ファイルを全て取得
string[] files = System.IO.Directory.GetFiles(".", "UserQuery_*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;
// ここで個人ビュー復元処理
}
個人ビューの復元
新しい環境のユーザーが特定できた時点で、データの復元を検討
していきます。
// ファイルから UserQuery をデシリアライズ
DataContractSerializer serializer = new DataContractSerializer(typeof(EntityCollection));
XmlReader reader = XmlReader.Create(file);
//XMLファイルから読み込み、デシリアライズする
EntityCollection views = (EntityCollection)serializer.ReadObject(reader);
//ファイルを閉じる
reader.Close();
// 取得した UserQuery を 1 件ずつ処理
foreach(var view in views.Entities)
{
// ここで個別の個人ビューを処理
}
これで view ローカル変数にデータを復元できました。次に
個人ビューを 1 件づつ復元していきますが、まずは所有者の
情報を新しいものに変更します。
// 取得したデータを UserQuery 型に変換
UserQuery userQuery = view.ToEntity<UserQuery>();
// 所有者を設定
userQuery.OwnerId = user.ToEntityReference();
次に、ビューがカスタムエンティティのものであった場合は、
前回の考察より LayoutXml の object 属性値が、環境によって
異なる可能性があることが分かっているため、更新を考えます。
カスタムエンティティ用のビューかは以下条件で判定します。
if(userQuery.ReturnedTypeCode.Contains("_"))
新しい環境のカスタムエンティティの ObjectTypeCode を以下の
メタデータクエリで取得します。以下のコードでは情報が取得
できなかった時のことは考えていません。新しい環境にすでに
ソリューションが適用済であると想定しています。
// エンティティフィルターを作成
MetadataFilterExpression EntityFilter = new MetadataFilterExpression(LogicalOperator.And);
// スキーマ名として個人ビューの ReturnedTypeCode を指定
EntityFilter.Conditions.Add(new MetadataConditionExpression("SchemaName", MetadataConditionOperator.Equals, userQuery.ReturnedTypeCode.ToString()));
// ObjectTypeCode だけをクエリ
MetadataPropertiesExpression EntityProperties = new MetadataPropertiesExpression()
{
AllProperties = false
};
EntityProperties.PropertyNames.Add("ObjectTypeCode");
// クエリの作成
EntityQueryExpression entityQueryExpression = new EntityQueryExpression()
{
Criteria = EntityFilter
};
// リクエストの作成
RetrieveMetadataChangesRequest retrieveMetadataChangesRequest = new RetrieveMetadataChangesRequest()
{
Query = entityQueryExpression
};
// エンティティのメタデータを取得
EntityMetadata meta = ((RetrieveMetadataChangesResponse)_serviceProxy.Execute(retrieveMetadataChangesRequest)).EntityMetadata.First();
int objectTypeCode = (int)meta.ObjectTypeCode;
これで新しい環境におけるカスタムエンティティの ObjectTypeCode が
分かったので、以下コードで LyaoutXml の値を差し替えます。
// LayoutXml の変更
var layoutxml = XDocument.Parse(userQuery.LayoutXml);
layoutxml.Root.Attribute("object").Value = objectTypeCode.ToString();
userQuery.LayoutXml = layoutxml.ToString();
最後に個人ビューを作成します。
_serviceProxy.Create(userQuery);
以下に全コードを示します。
// フォルダ内で UserQuery で始まる XML ファイルを全て取得
string[] files = System.IO.Directory.GetFiles(".", "UserQuery_*xml", System.IO.SearchOption.AllDirectories);
// それぞれのファイルごとに処理
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;
// ファイルから UserQuery をデシリアライズ
// DataContractSerializer を前回と同じタイプで生成
DataContractSerializer serializer = new DataContractSerializer(typeof(EntityCollection));
// XML リーダーを生成しファイルから読み込み
XmlReader reader = XmlReader.Create(file);
//XMLファイルから読み込み、デシリアライズする
EntityCollection views = (EntityCollection)serializer.ReadObject(reader);
//ファイルを閉じる
reader.Close();
// 取得した UserQuery を 1 件ずつ処理
foreach(var view in views.Entities)
{
// 取得したデータを UserQuery 型に変換
UserQuery userQuery = view.ToEntity<UserQuery>();
// 所有者を設定
userQuery.OwnerId = user.ToEntityReference();
// カスタムエンティティの場合 (エンティティ名にアンダースコアがある場合)
if(userQuery.ReturnedTypeCode.Contains("_"))
{
// エンティティフィルターを作成
MetadataFilterExpression EntityFilter = new MetadataFilterExpression(LogicalOperator.And);
// スキーマ名として個人ビューの ReturnedTypeCode を指定
EntityFilter.Conditions.Add(new MetadataConditionExpression("SchemaName", MetadataConditionOperator.Equals, userQuery.ReturnedTypeCode.ToString()));
// ObjectTypeCode だけをクエリ
MetadataPropertiesExpression EntityProperties = new MetadataPropertiesExpression()
{
AllProperties = false
};
EntityProperties.PropertyNames.Add("ObjectTypeCode");
// クエリの作成
EntityQueryExpression entityQueryExpression = new EntityQueryExpression()
{
Criteria = EntityFilter
};
// リクエストの作成
RetrieveMetadataChangesRequest retrieveMetadataChangesRequest = new RetrieveMetadataChangesRequest()
{
Query = entityQueryExpression
};
// エンティティのメタデータを取得
EntityMetadata meta = ((RetrieveMetadataChangesResponse)_serviceProxy.Execute(retrieveMetadataChangesRequest)).EntityMetadata.FirstOrDefault();
int objectTypeCode = (int)meta.ObjectTypeCode;
// LayoutXml の変更
var layoutxml = XDocument.Parse(userQuery.LayoutXml);
layoutxml.Root.Attribute("object").Value = objectTypeCode.ToString();
userQuery.LayoutXml = layoutxml.ToString();
}
// 個人ビューの作成
_serviceProxy.Create(userQuery);
}
}
その他の考察
前回と今回のコード内での懸念点を以下の通りです。
- ユーザーの特定を DomainName で行えるか。たとえばオンライン
環境のようにドメインが変わる場合は他のフィールドで特定が必要。
- 個人ビュー作成時に GUID が指定されている。ベストプラクティス
では Microsoft Dynamics CRM サーバーに Guid の付与は任せるべき
であるためデシリアライズしたものをそのまま利用せず、新しい
インスタンスに各種値だけ渡して処理すべきか。
- 既に同じ名前のビューが存在する場合の重複処理、また各種処理
で失敗した場合のハンドリングを考えていない。等。
まとめ
既定のエクスポート、インポートで移動できないデータは、SDK を
利用して移動するか、手動で再作成する必要があります。その際
数々の考慮が必要であることが今回の記事から読み取れます。
試行錯誤が必要になると思いますが、是非チャレンジしてください!
- 中村 憲一郎