Dynamics CRM 2011 監査履歴より削除したレコードを復元する

みなさん、こんにちは。

今回は開発者および管理者、同時に関連する内容を案内します。

ユーザーがデータを誤って削除した場合、データの復旧は管理者に
とって重要なタスクだと思います。手段はいくつかありますが、以下の
ような方法が一般的だと思います。

‐ 設置型の場合はデータベースのバックアップより復旧
‐ インポート元のデータがある場合には、再インポート
‐ 手動でレコードの再作成

それぞれメリット/デメリットがあると思いますが、今回はその他の
手段として監査ログの利用を紹介したいと思います。

監査ログを利用してレコードを復元する場合には、以下の点に
留意してください。

‐ メモのように監査に対応していないエンティティには利用できない。
‐ 監査に対応しいてるエンティティは、事前に監査ログを設定しており、
   該当レコードの削除監査がある。
‐ 監査していないフィールドの値は復元できない
‐ 関連レコードの復元は、親レコードから行う必要がある

単体のレコードの復元

前提として、組織レベルでの監査が有効で、かつ取引先企業に対する
監査が設定されているとします。その場合、取引先企業レコードを削除
すると、以下のような監査レコードが作成されます。

[取引先企業レコード]
image

[上記を削除した際の監査レコード]
image

上記から分かるように、監査対象のフィールドに関して、削除時点で
保有していた値が記録されています。では早速その取得とレコードの
再作成に利用できるコードサンプルを紹介します。

1. 監査レコードを最大化して、URL より監査レコードの ID を確認します。
id パラメータの %7b、%7d の間の値です。

image

2. 以下のコードを利用して監査の詳細を取得します。

// 監査レコードの GUID を設定
Guid auditId = new Guid("F0380F4E-1A17-E111-8CB2-00155D40BA17");
// RetrieveAuditDetailsRequest を作成
RetrieveAuditDetailsRequest request =new RetrieveAuditDetailsRequest();
// 取得した ID を指定
request.AuditId = auditId;
// AuditDetail の取得
RetrieveAuditDetailsResponse response = (RetrieveAuditDetailsResponse) _service.Execute(request);
// 取得した response に含まれる AuditDetails を AttributeAuditDetail
// にキャストして、OldValue から削除時点の情報をエンティティとして取得
// Create メソッドでレコードを再作成
_service.Create(((AttributeAuditDetail)response.AuditDetail).OldValue);

3. こちらのコードを利用すれば、取引先企業レコードを復元できます。

関連したレコードの復元 (上位下位の関連)

次に、取引先企業にサポート案件が紐付いている場合を考えます。この場合
親レコードである取引先企業を削除すると、子レコードに相当するサポート案件も
同時に削除されます。

先ほど取引先企業にサポート案件が存在する場合で、サポート案件も監査対象
となっている場合、監査ログには以下のようにレコードが表示されます。

image

この場合これらの二つの監査は、異なる監査レコード ID を持ちますが、一方で
同一のトランザクション ID を持っています。よって、いずれかの監査レコード ID より
トランザクション ID を取得し、同じトランザクション ID を持つ監査レコードを取得、
順次レコードを復旧することになります。

1. 親レコードの監査レコードを開きます。URL より監査レコードの ID を
確認してください。

image

2. 次のコードより、トランザクション ID を取得できます。

// 監査レコードの GUID を設定
Guid auditId = new Guid("B6A22107-1C17-E111-8CB2-00155D40BA17");
// Retrieve メソッドより、Audit エンティティ、Transactionid 列を指定してデータ取得
Audit audit = (Audit)_service.Retrieve(Audit.EntityLogicalName, auditId, new ColumnSet("transactionid"));
// 取得した結果より、 TransactionId を取得
Guid transactionId = (Guid)audit.TransactionId;

3. 取得した TransactionId と RetrieveMultiple を利用して、復元対象
となる監査レコードを全て取得。

// 今回は QueryExpression を利用
QueryExpression qe = new QueryExpression()
{
    // エンティティ名として Audit を指定
    EntityName = Audit.EntityLogicalName,
    // 条件として取得した TransactionId を指定
    Criteria = { Conditions =
        { new ConditionExpression("transactionid", ConditionOperator.Equal, transactionId) } }
};

// RetrieveMultipleRequest でデータを取得
RetrieveMultipleRequest retrieveMultipleRequest = new RetrieveMultipleRequest()
{
    Query = qe
};

EntityCollection retrieveMultipleResponse =
((RetrieveMultipleResponse)_serviceProxy.Execute(retrieveMultipleRequest)).EntityCollection;

4. 結果より順次レコードを復元。

foreach (Audit au in retrieveMultipleResponse.Entities)
{
    // 取得した AuditId を利用して AuditDetail を取得
    RetrieveAuditDetailsRequest request = new RetrieveAuditDetailsRequest();
    request.AuditId = (Guid)au.AuditId;

    RetrieveAuditDetailsResponse response = (RetrieveAuditDetailsResponse)_service.Execute(request);
    // 取得した response に含まれる AuditDetails を AttributeAuditDetail
    // にキャストして、OldValue から削除時点の情報をエンティティとして取得
    // Create メソッドでレコードを再作成
    _service.Create(((AttributeAuditDetail)response.AuditDetail).OldValue);
}    

5. 実行後、監査ログで以下のようにレコードが表示されます。

image

関連したレコードの復元 (参照の関連)

参照の関連がある場合には、対応が少し変わります。ここでは
取引先企業と取引先担当者に対して、参照の 1:N 関連を設定
してみました。また監査対象としています。

image

image

image

image

取引先企業を削除すると、監査ログは以下のようになります。
取引先企業とサポート案件は削除されていますが、担当者は
更新となっており、取引先企業へのリンクだけが削除されています。

image

利用するコードはほぼ同じですが、最後の部分だけ異なり、
レコードの更新か、作成かを考慮しなくてはなりません。

// 監査レコードの GUID を設定
Guid auditId = new Guid("B6E76135-3017-E111-8CB2-00155D40BA17");

// Retrieve メソッドより、Audit エンティティ、Transactionid 列を指定してデータ取得
Audit audit = (Audit)_service.Retrieve(Audit.EntityLogicalName, auditId, new ColumnSet("transactionid"));
// 取得した結果より、 TransactionId を取得
Guid transactionId = (Guid)audit.TransactionId;

// 今回も QueryExpression を利用
QueryExpression qe = new QueryExpression()
{
    // エンティティ名として Audit を指定
    EntityName = Audit.EntityLogicalName,
    // 既定では ID しか取得しないので、取得列に Operation も追加
    ColumnSet = new ColumnSet("operation"),
    // 条件として取得した TransactionId を指定
    Criteria = { Conditions =
        { new ConditionExpression("transactionid", ConditionOperator.Equal, transactionId) } }
};

// RetrieveMultipleRequest でデータを取得
RetrieveMultipleRequest retrieveMultipleRequest = new RetrieveMultipleRequest()
{
    Query = qe
};

EntityCollection retrieveMultipleResponse =
((RetrieveMultipleResponse)_serviceProxy.Execute(retrieveMultipleRequest)).EntityCollection;

foreach (Audit au in retrieveMultipleResponse.Entities)
{
    // 取得した AuditId を利用して AuditDetail を取得
    RetrieveAuditDetailsRequest request = new RetrieveAuditDetailsRequest();
    request.AuditId = (Guid)au.AuditId;

    RetrieveAuditDetailsResponse response = (RetrieveAuditDetailsResponse)_service.Execute(request);

    // Audit の Operation を確認。削除の場合 (3) はレコードを再作成
    if (au.Operation.Value == 3)
        _service.Create(((AttributeAuditDetail)response.AuditDetail).OldValue);
    // 更新の場合 (2) はレコードを再更新
    else if (au.Operation.Value == 2)
        _service.Update(((AttributeAuditDetail)response.AuditDetail).OldValue);
}

結果は以下のように監査ログが出力され、レコードは復元されます。

image

まとめ

監査を設定している場合には、監査ログにはレコード操作の
記録が残っています。今回はそのデータを利用したレコードの
復旧を紹介しました。

実際にはメモをはじめ監査に対応していないエンティティがあることや
より複雑な関連がある場合には利用できませんが、選択肢の 1 つと
してご利用ください。

- Dynamics CRM サポート 中村 憲一郎