PowerPlatform、モデル駆動型の研究

ジン・フリークス。日本人。

PowerAppsモデル駆動型JavaScript APIのツリー構造

PowerApps Model-Driven JavaScript API

Xrm (ルートオブジェクト)
├── FormContext (フォームコンテキスト)
|   ├── Attributes (属性:データフィールド操作)
|   |   ├── getAttribute (メソッド: フィールドにアクセス)
|   |   |   ├── getValue / setValue (メソッド: 値の取得・設定)
|   |   |   |   サンプル:
|   |   |   |   ```javascript
|   |   |   |   var fieldValue = formContext.getAttribute("fieldname").getValue();
|   |   |   |   formContext.getAttribute("fieldname").setValue("newValue");
|   |   |   |   ```
|   |   |   |   説明: `fieldname` フィールドの現在の値を取得し、続けて新しい値 "newValue" を設定します。
|   |   |   ├── getRequiredLevel / setRequiredLevel (メソッド: 必須設定)
|   |   |   |   サンプル:
|   |   |   |   ```javascript
|   |   |   |   formContext.getAttribute("fieldname").setRequiredLevel("required");
|   |   |   |   ```
|   |   |   |   説明: `fieldname` フィールドを必須項目として設定します。
|   |   |   ├── addOnChange / removeOnChange (メソッド: イベントハンドラ追加・削除)
|   |   |   |   サンプル:
|   |   |   |   ```javascript
|   |   |   |   formContext.getAttribute("fieldname").addOnChange(function() {
|   |   |   |       console.log("Value changed!");
|   |   |   |   });
|   |   |   |   ```
|   |   |   |   説明: `fieldname` フィールドの値が変更された際に「Value changed!」というメッセージをコンソールに出力する処理を追加します。
|   |   |   ├── isDirty (プロパティ: フィールドが編集されているかの確認)
|   |   |   |   サンプル:
|   |   |   |   ```javascript
|   |   |   |   var isFieldDirty = formContext.getAttribute("fieldname").isDirty;
|   |   |   |   if (isFieldDirty) {
|   |   |   |       console.log("Field has unsaved changes.");
|   |   |   |   }
|   |   |   |   ```
|   |   |   |   説明: `fieldname` フィールドに未保存の変更がある場合、「Field has unsaved changes.」というメッセージをコンソールに表示します。
|   ├── Controls (UIコントロール)
|   |   ├── getControl (メソッド: コントロールにアクセス)
|   |   |   ├── setVisible / getVisible (メソッド: 表示・非表示設定)
|   |   |   |   サンプル:
|   |   |   |   ```javascript
|   |   |   |   formContext.getControl("controlname").setVisible(true);
|   |   |   |   ```
|   |   |   |   説明: `controlname` コントロールを表示させます。
|   |   |   ├── setDisabled / getDisabled (メソッド: 有効・無効設定)
|   |   |   |   サンプル:
|   |   |   |   ```javascript
|   |   |   |   formContext.getControl("controlname").setDisabled(false);
|   |   |   |   ```
|   |   |   |   説明: `controlname` コントロールを有効にします(ユーザーが編集可能になります)。
|   |   |   ├── setFocus (メソッド: フォーカス設定)
|   |   |   |   サンプル:
|   |   |   |   ```javascript
|   |   |   |   formContext.getControl("controlname").setFocus();
|   |   |   |   ```
|   |   |   |   説明: `controlname` コントロールにフォーカスを設定し、ユーザーがすぐに入力できるようにします。
|   ├── save (メソッド: フォームのデータ保存)
|   |   サンプル:
|   |   ```javascript
|   |   formContext.data.save().then(
|   |       function success() { console.log("Data saved"); },
|   |       function(error) { console.error(error.message); }
|   |   );
|   |   ```
|   |   説明: フォームの現在のデータを保存し、成功した場合は「Data saved」を、エラーが発生した場合はエラーメッセージをコンソールに出力します。
|   ├── refresh (メソッド: データの再読み込み)
|   |   サンプル:
|   |   ```javascript
|   |   formContext.data.refresh().then(
|   |       function success() { console.log("Data refreshed"); },
|   |       function(error) { console.error(error.message); }
|   |   );
|   |   ```
|   |   説明: フォームデータをサーバーから再読み込みし、成功した場合は「Data refreshed」を、エラーが発生した場合はエラーメッセージをコンソールに出力します。
|   ├── setFormNotification / clearFormNotification (メソッド: フォームレベルの通知設定・解除)
|   |   サンプル:
|   |   ```javascript
|   |   formContext.ui.setFormNotification("This is a form-level notification", "INFO", "uniqueID");
|   |   formContext.ui.clearFormNotification("uniqueID");
|   |   ```
|   |   説明: フォーム全体に情報レベルの通知を表示し、その後に `uniqueID` を指定して通知を解除します。
|
└── WebApi (データAPI)
    ├── retrieveRecord (メソッド: データの取得)
    |   サンプル:
    |   ```javascript
    |   Xrm.WebApi.retrieveRecord("account", "account-id", "?$select=name").then(
    |       function success(result) { console.log(result.name); },
    |       function(error) { console.error(error.message); }
    |   );
    |   ```
    |   説明: `account` エンティティの特定のIDに対応するレコードを取得し、`name` フィールドの値をコンソールに出力します。
    ├── createRecord (メソッド: データの作成)
    |   サンプル:
    |   ```javascript
    |   var accountData = { name: "New Account" };
    |   Xrm.WebApi.createRecord("account", accountData).then(
    |       function success(result) { console.log("ID: " + result.id); },
    |       function(error) { console.error(error.message); }
    |   );
    |   ```
    |   説明: 新しい `account` レコードを作成し、作成されたレコードのIDをコンソールに出力します。
    ├── updateRecord (メソッド: データの更新)
    |   サンプル:
    |   ```javascript
    |   var accountUpdate = { name: "Updated Name" };
    |   Xrm.WebApi.updateRecord("account", "account-id", accountUpdate).then(
    |       function success() { console.log("Updated"); },
    |       function(error) { console.error(error.message); }
    |   );
    |   ```
    |   説明: `account-id` に対応する `account` レコードを更新し、更新が成功した場合は「Updated」を出力します。
    ├── deleteRecord (メソッド: データの削除)
    |   サンプル:
    |   ```javascript
    |   Xrm.WebApi.deleteRecord("account", "account-id").then(
    |       function success() { console.log("Deleted"); },
    |       function(error) { console.error(error.message); }
    |   );
    |   ```
    |   説明: `account-id` に対応する `account` レコードを削除し、削除が成功した場合は「Deleted」を出力します。

プラグインの種類とツリー構造

PowerAppsモデル駆動型アプリケーションで利用できるプラグインの種類をツリー構造で整理

 

PowerApps Model-Driven Plugins

├── Pre-Validation (事前検証プラグイン)
|   ├── 説明: エンティティ操作の前にビジネスルールやデータの整合性を検証するために使用されます。
|   ├── 主な用途:
|   |   - カスタムバリデーションの実施
|   |   - 他のエンティティや関連データの状態を確認
|   ├── 実行タイミング: トランザクションの開始前(データベースにアクセスする前)
|   └── サンプル処理:
|       - サンプル: 新しいアカウントを作成する前に、名前が重複していないか検証
|       ```csharp
|       if (CheckDuplicateName(accountName)) {
|           throw new InvalidPluginExecutionException("Account name already exists.");
|       }
|       ```

├── Pre-Operation (事前処理プラグイン)
|   ├── 説明: データがデータベースに保存される直前に処理を実行するためのプラグインです。
|   ├── 主な用途:
|   |   - データの初期化や加工
|   |   - 自動で値を設定、またはデータを標準化
|   ├── 実行タイミング: データベース更新の直前
|   └── サンプル処理:
|       - サンプル: アカウントの作成時に「作成日」フィールドを自動設定
|       ```csharp
|       entity["createdOn"] = DateTime.UtcNow;
|       ```

├── Post-Operation (事後処理プラグイン)
|   ├── 説明: データが保存された後に追加の処理を行うためのプラグインです。
|   ├── 主な用途:
|   |   - 他のエンティティへのデータ転送や連携
|   |   - ワークフローや通知のトリガー
|   ├── 実行タイミング: データベース更新後(トランザクションの後)
|   └── サンプル処理:
|       - サンプル: 新規アカウント作成後に、関連する連絡先エンティティを自動作成
|       ```csharp
|       var contact = new Entity("contact");
|       contact["parentcustomerid"] = new EntityReference("account", accountId);
|       service.Create(contact);
|       ```

├── Asynchronous Plugins (非同期プラグイン)
|   ├── 説明: メインの操作後に非同期で実行されるプラグインです。ユーザーの操作に影響を与えないために使用されます。
|   ├── 主な用途:
|   |   - 時間のかかる処理や外部サービスとの連携
|   |   - 定期的なデータ更新や通知の送信
|   ├── 実行タイミング: メインの操作終了後、システムリソースが空いているタイミング
|   └── サンプル処理:
|       - サンプル: アカウントの更新時に、外部APIを呼び出して顧客データを更新
|       ```csharp
|       await CallExternalService(accountId);
|       ```

├── Synchronous Plugins (同期プラグイン)
|   ├── 説明: 操作が完了する前に、トランザクション内で即時に実行されるプラグインです。
|   ├── 主な用途:
|   |   - 即座にデータ検証や変換を行いたい場合
|   |   - システム内で一貫性を保つためのチェック
|   ├── 実行タイミング: メイン操作中にリアルタイムで実行
|   └── サンプル処理:
|       - サンプル: 新規注文の保存時に在庫数をチェックし、不足している場合エラーメッセージを表示
|       ```csharp
|       if (!CheckInventory(productId, quantity)) {
|           throw new InvalidPluginExecutionException("Insufficient inventory.");
|       }
|       ```

└── Image Plugins (イメージプラグイン)
    ├── 説明: プラグインで操作する前後のエンティティデータの「スナップショット」を取得し、変更内容を追跡するために使用します。
    ├── 主な用途:
    |   - 更新前と更新後のデータ比較
    |   - ログの記録や監査目的
    ├── 実行タイミング: 操作前(Pre-Image)または操作後(Post-Image)で選択可能
    └── サンプル処理:
        - サンプル: 更新前と更新後の住所データを比較して変更があればログに記録
        ```csharp
        if (preImage["address1_line1"] != postImage["address1_line1"]) {
            LogAddressChange(preImage, postImage);
        }
        ```

のクライアントスクリプトの一連の処理の流れ

 

Power Apps モデル駆動型アプリでのクライアントスクリプトの一連の処理の流れ(詳細版)

Power Apps のモデル駆動型アプリでクライアントスクリプトを使用する際の詳しい処理の流れを解説します。これにより、アプリケーションの動作を深く理解し、効率的な開発を行うことができます。

目次

  1. ユーザー操作
  2. クライアントスクリプトの実行
  3. データの前処理とパッケージング
  4. データの送信(Web APIへのリクエスト)
  5. ネットワーク通信とセキュリティ
  6. Web APIによるデータ受信と処理
  7. サーバーからの応答(Web APIのレスポンス)
  8. 応答の受信とクライアントでの処理
  9. ユーザーへのフィードバック
  10. 後続処理
  11. 追加のポイントや考慮事項
  12. 全体のフロー図(詳細版)
  13. 補足情報

1. ユーザー操作

ユーザーがモデル駆動型アプリのフォームにデータを入力し、「保存」ボタンをクリックします。

2. クライアントスクリプトの実行

フォームのイベント(例:onSaveonLoadonChange)に紐づけられたJavaScript関数が実行されます。

  • 処理内容:
    • フィールドの値の取得と設定
    • 入力データの検証(必須項目のチェック、データ形式の確認など)
    • ビジネスルールの適用(特定の条件下でフィールドを読み取り専用にするなど)
    • 非同期処理の実行(他のデータの取得や外部サービスとの通信)
  • 保存のキャンセルやエラーメッセージの表示が可能

サンプルコード:

 function onSave(executionContext) { var formContext = executionContext.getFormContext(); var requiredField = formContext.getAttribute("new_requiredfield").getValue(); if (!requiredField) { formContext.ui.setFormNotification("必須フィールドが入力されていません。", "ERROR", "1"); executionContext.getEventArgs().preventDefault(); } } 

3. データの前処理とパッケージング

  • フォーム上の全てのフィールド値を収集
  • データを適切な形式(JSON)にシリアライズ
  • システムフィールド(作成者、作成日時など)の自動補完
  • 一意の識別子(GUID)の生成(新規レコードの場合)

4. データの送信(Web APIへのリクエスト)

  • HTTPリクエストを作成
    • メソッド: 新規作成は POST、更新は PATCH
    • ヘッダー:
      • Authorization: 認証トークン(OAuth 2.0ベアラートークン)
      • Content-Type: application/json; charset=utf-8
    • ボディ: シリアライズされたJSONデータ
  • エンドポイントURL:
    • 新規作成: https://<your_org>.crm.dynamics.com/api/data/v9.2/<entitysetname>
    • 更新: https://<your_org>.crm.dynamics.com/api/data/v9.2/<entitysetname>(<recordId>)

サンプルリクエスト:

 POST https://your_org.crm.dynamics.com/api/data/v9.2/accounts HTTP/1.1 Authorization: Bearer eyJ0eXAiOiJKV1QiL... Content-Type: application/json; charset=utf-8 { "name": "新しい取引先企業", "telephone1": "03-1234-5678" } 

5. ネットワーク通信とセキュリティ

  • HTTPSを使用した暗号化通信によりデータを送信
  • TLS/SSLによるセキュアなデータ転送
  • OAuth 2.0による認証と認可の実施
    • アクセストークンの有効性とユーザー権限の確認

6. Web APIによるデータ受信と処理

  • リクエストを受信し、ヘッダーとボディを解析
  • 認証情報の検証
    • トークンの有効期限やスコープの確認
  • データの検証
    • 必須フィールドのチェック
    • データ型やフォーマットの確認
  • 業務ルールの適用
  • データベース操作

7. サーバーからの応答(Web APIのレスポンス)

  • HTTPステータスコードの設定
    • 成功: 200 OK201 Created204 No Content
    • クライアントエラー: 400 Bad Request401 Unauthorized403 Forbidden404 Not Found
    • サーバーエラー: 500 Internal Server Error
  • レスポンスヘッダーの設定
    • OData-VersionContent-Type など
  • レスポンスボディの生成
    • エラー時: エラーコードとメッセージを含むJSONオブジェクト
    • 成功時: 新規作成の場合は新しいレコードのURIやIDを含む

サンプルレスポンス:

 HTTP/1.1 201 Created OData-Version: 4.0 Content-Type: application/json; charset=utf-8 Location: https://your_org.crm.dynamics.com/api/data/v9.2/accounts(00000000-0000-0000-0000-000000000001) { "@odata.context": "https://your_org.crm.dynamics.com/api/data/v9.2/$metadata#accounts/$entity", "accountid": "00000000-0000-0000-0000-000000000001", "name": "新しい取引先企業", "telephone1": "03-1234-5678" } 

8. 応答の受信とクライアントでの処理

  • クライアントがHTTPレスポンスを受信
  • ステータスコードとレスポンスボディの解析
  • 非同期処理の場合、Promiseやコールバックで結果を処理
  • エラーの場合は詳細情報を取得し、適切な対処を実施

9. ユーザーへのフィードバック

  • 成功時:
    • 保存完了のメッセージを表示
    • フォームのリセットや別ページへのナビゲーション
  • エラー時:
    • エラーメッセージの表示(具体的な原因を含める)
    • 問題のあるフィールドをハイライト
    • 再入力や再試行を促す

10. 後続処理

  • 必要に応じて追加のクライアントスクリプトを実行
    • 関連レコードの更新
    • 外部サービスへの通知
  • ワークフローやフローのトリガー
    • データの保存後に自動的に実行されるプロセス

11. 追加のポイントや考慮事項

非同期処理の扱い

  • クライアントスクリプトでWeb APIを直接呼び出す場合、非同期処理になるため、Promiseasync/awaitを適切に使用する必要があります。
  • フォームの保存処理と同期させたい場合、Xrm.Utility.promiseEventを使用して保存プロセスを制御できます。

エラーハンドリングの強化

  • エラー発生時にユーザーが具体的な対処方法を理解できるよう、エラーメッセージを工夫します。
  • ネットワークエラーやタイムアウトへの対応も考慮します。

パフォーマンスの最適化

  • 不要なフィールドやデータを送信しないようにします。
  • クライアントスクリプト内での処理を最小限に抑え、レスポンスを向上させます。

セキュリティの強化

  • クライアントサイドで機密情報を扱わないように注意します。
  • サーバーサイドでのデータ検証を徹底し、不正なデータの登録を防止します。

デバッグとロギング

  • 開発時にはブラウザのデベロッパーツールを活用して、スクリプトの動作やネットワーク通信を確認します。
  • サーバーサイドではプラグインのトレースログを活用して問題の特定を行います。

プラグインやカスタムワークフローの利用

  • 複雑なビジネスロジックやデータ処理が必要な場合、C#プラグインやカスタムワークフローを作成し、Dataverseに登録します。
  • プラグインの登録ステージ(プリバリデーション、プリ操作、ポスト操作)を適切に選択します。

12. 全体のフロー図(詳細版)

以下は、一連の処理の全体的な流れをまとめたフロー図です。

  1. ユーザー操作
  2. クライアントスクリプトの実行
  3. データの前処理とパッケージング
  4. データの送信(Web APIへのリクエスト)
  5. ネットワーク通信とセキュリティ
  6. Web APIによるデータ受信と処理
  7. サーバーからの応答(Web APIのレスポンス)
  8. 応答の受信とクライアントでの処理
  9. ユーザーへのフィードバック
  10. 後続処理

13. 補足情報

DataverseのWeb APIの活用

  • 外部アプリケーションやサービスからもWeb APIを利用してDataverseのデータにアクセスできます。
  • Azure Functionsやロジックアプリと組み合わせて、拡張性の高いソリューションを構築できます。

認証と認可の詳細

  • Azure Active Directory(Azure AD)を使用した認証が行われます。
  • アプリケーション登録や権限の設定が必要な場合があります。

エンティティとフィールドのメタデータ

  • エンティティ名やフィールド名は、ソリューション内で定義されたスキーマ名を使用します。
  • メタデータを取得することで、ダイナミックなアプリケーションを構築できます。

環境の違いへの対応

  • 開発環境、テスト環境、本番環境でエンドポイントや認証情報が異なる場合があります。
  • 環境ごとの設定を管理しやすくする工夫が必要です。

まとめ

Power Apps モデル駆動型アプリでクライアントスクリプトを使用する際の一連の処理の流れを詳細に説明しました。クライアントスクリプトはユーザーエクスペリエンスを向上させ、ビジネス要件に合わせた柔軟なカスタマイズを可能にします。正しい理解と実装により、効率的で効果的なアプリケーション開発を実現しましょう。

参考資料

モデル駆動型アプリで「タブの表示制御」を活用しよう

 

PowerAppsモデル駆動型アプリで「タブの表示制御」を活用しよう

目次

  1. 概要
  2. PowerAppsでのタブ表示制御の重要性
  3. タブ表示制御のメリット
  4. 具体例:役職に応じたタブの表示
  5. 実装例とコードの比較
  6. 関連メソッドと構文
  7. 技術的な豆知識と制限事項
  8. 実践問題

概要

本記事では、PowerAppsモデル駆動型アプリでの「タブの表示制御」を活用する方法について解説します。 タブ表示を役職や権限に応じて制御することで、アプリがより安全で、ユーザーにとって分かりやすい構成になります。 具体的な例として、ユーザーの役職が「マネージャー」の場合のみ、特定のタブを表示する方法を紹介します。

PowerAppsでのタブ表示制御の重要性

PowerAppsでは、表示する内容をユーザーの役職や権限に応じて制御することができます。 特定の役職のみに機密データを表示したり、一般社員には関係のない管理情報を非表示にすることで、操作性やセキュリティを向上させることが可能です。

タブ表示制御のメリット

  1. アクセス制御: 特定の役職や権限のユーザーのみがデータにアクセス可能になり、セキュリティが向上します。
  2. UIの簡素化: 必要な情報だけを表示することで、操作しやすく、画面がシンプルになります。

具体例:役職に応じたタブの表示

ここでは、ユーザーが「マネージャー」役職を持っている場合にのみ「manager_tab」を表示する例を紹介します。

データ仕様

  • テーブル名: userRoles
  • 列名とデータ型:
    • roleName: テキスト(役職名)

実装例とコードの比較

タブ表示制御なしの場合


function onFormLoad(executionContext) {
    var formContext = executionContext.getFormContext();
    // すべてのユーザーに「manager_tab」を表示
    formContext.ui.tabs.get("manager_tab").setVisible(true);
}

問題点: このコードでは、ユーザーの役職に関係なくすべてのユーザーに「manager_tab」が表示されてしまい、情報が制御されていません。

役職に応じたタブの表示制御(async/awaitを使用)


async function onFormLoadWithRoleCheck(executionContext) {
    var formContext = executionContext.getFormContext();

    // ユーザーの役職情報を取得
    const userRoles = Xrm.Utility.getGlobalContext().userSettings.roles;
    const isManager = userRoles.some(role => role.name === "Manager");

    // マネージャーのみ「manager_tab」を表示
    formContext.ui.tabs.get("manager_tab").setVisible(isManager);
}

メリット: 非同期処理を使い、役職情報を取得して役職に応じてタブを表示することで、ユーザー体験が向上し、必要な情報だけが見られるようになります。

関連メソッドと構文

タブ表示制御に使用する主なメソッドと構文を解説します。

メソッド一覧

  • Xrm.Utility.getGlobalContext(): PowerAppsで現在のユーザー情報や環境情報を取得するメソッドです。
    使用例: const context = Xrm.Utility.getGlobalContext();
  • someメソッド: 配列の中に条件を満たす要素があるかどうかを判定するメソッドです。
    使用例: userRoles.some(role => role.name === "Manager"); これは、userRoles配列の中に「Manager」という役職があるかを確認します。
  • setVisible: 特定のUI要素の表示/非表示を設定するメソッドです。
    使用例: formContext.ui.tabs.get("manager_tab").setVisible(isManager); ここで、isManagerがtrueであれば「manager_tab」を表示し、falseであれば非表示にします。

注意点: 非同期処理で`await`を使う場合、必ず`async`関数内でのみ使用できることに注意してください。

技術的な豆知識と制限事項

豆知識

  • async/await: 非同期処理を同期的に見せる記法で、コードが読みやすくなります。

制限事項

  • PowerAppsのタブ表示制御はクライアント側で行われるため、権限の制御は完全ではありません。セキュリティが特に重要な場合は、サーバー側での制御を検討する必要があります。

実践問題

問題: フォームロード時に「Manager」または「Admin」役職がある場合にのみ特定のタブを表示する関数onFormLoadWithExtendedRoleCheckを実装してください。

  • 条件: 役職情報を非同期で取得し、ManagerまたはAdminの役職がある場合にタブを表示する。

この記事では、PowerAppsモデル駆動型アプリでの「タブの表示制御」の活用について解説しました。タブの表示制御をうまく活用して、必要なユーザーだけがアクセスできるようにし、アプリのセキュリティと使いやすさを向上させましょう。

PowerAppsモデル駆動型アプリで「非同期データ取得」を活用しよう

 

PowerAppsモデル駆動型アプリで「非同期データ取得」を活用しよう

目次

  1. 概要
  2. PowerAppsでの非同期データ取得の重要性
  3. 非同期処理のメリット
  4. 具体例:顧客データの非同期取得
  5. 実装例とコードの比較
  6. 関連メソッドと構文
  7. 技術的な豆知識と制限事項
  8. 実践問題

概要

本記事では、PowerAppsモデル駆動型アプリにおける非同期データ取得とその利点について解説します。 非同期処理を活用することで、ユーザーがデータ取得中もスムーズに操作を続けられるようになり、アプリの使い勝手が向上します。 この記事では、顧客データの非同期取得を具体例として、処理の流れと実装方法について詳しく説明します。

PowerAppsでの非同期データ取得の重要性

PowerAppsでは、サーバーからデータを取得して表示するシーンが頻繁にあります。 もしデータ取得が同期処理で行われると、取得が完了するまでユーザーの操作が一時的に止まり、快適な操作が妨げられます。 非同期処理を利用することで、データ取得の完了を待たずに他の操作が可能になり、アプリの応答性が向上します。

非同期処理のメリット

  1. 応答性の向上: データ取得中も操作を続けられるため、ユーザーエクスペリエンスが向上します。
  2. 効率的なリソース利用: 複数のデータ取得を同時に行うことで、リソースを効率的に使用できます。
  3. エラーハンドリングが容易: 非同期処理では、エラー時の対応が簡素化され、安定性が向上します。

具体例:顧客データの非同期取得

ここでは、顧客データを非同期で取得し、ユーザーがデータ取得完了を待たずに操作を続けられる実装を紹介します。

データ仕様

  • テーブル名: contact
  • 列名とデータ型:
    • fullname: テキスト
    • email: メールアドレス
    • phone: 電話番号

実装例とコードの比較

非同期処理を使わない場合


function getContactDataSync() {
    var contactId = "some-contact-id";
    var result = Xrm.WebApi.retrieveRecord("contact", contactId, "?$select=fullname,email,phone");

    console.log("顧客名:", result.fullname);
    console.log("メールアドレス:", result.email);
    console.log("電話番号:", result.phone);
}

問題点: このコードはデータ取得が完了するまで操作が止まってしまいます。同期処理では、ユーザーの待機時間が発生し、使い勝手が悪くなります。

非同期処理(async/awaitを使用)を使ったコード


async function getContactDataAsync() {
    var contactId = "some-contact-id";
    
    try {
        const result = await Xrm.WebApi.retrieveRecord("contact", contactId, "?$select=fullname,email,phone");
        console.log("顧客名:", result.fullname);
        console.log("メールアドレス:", result.email);
        console.log("電話番号:", result.phone);
    } catch (error) {
        console.error("データ取得エラー:", error.message);
    }
}

メリット: 非同期でデータ取得が行われ、操作がスムーズに行えるようになります。awaitにより、データ取得の完了を待ちながらも、他の操作が可能です。

関連メソッドと構文

非同期処理で利用する主なメソッドと構文について説明します。

メソッド一覧

  • Xrm.WebApi.retrieveRecord: 指定したテーブルとIDでデータを取得します。
    使用例: Xrm.WebApi.retrieveRecord("contact", contactId, "?$select=fullname,email,phone");
  • async/await: 非同期関数と、その完了を待つためのキーワード。

注意点: awaitasync関数内でのみ使用可能です。

技術的な豆知識と制限事項

豆知識

  • async/await: 非同期処理を同期的に見せる記法で、コードが読みやすくなります。

制限事項

  • 通信が不安定だとデータ取得が遅延する可能性があります。

 

コード全体の流れ

この記事で使われているコードは、PowerAppsモデル駆動型アプリで、**「顧客データを非同期で取得して表示する」**という内容です。同期と非同期の2つの方法を使って、データの取得がアプリの動作にどう影響するかを説明しています。では、それぞれのコードの流れを順を追って見ていきましょう。


同期処理の流れ:getContactDataSync()

まず、同期処理を使った例について説明します。この関数は、PowerAppsの「Xrm.WebApi.retrieveRecord」メソッドを使ってデータを取得しますが、処理の仕方に問題があります。

処理の流れ

  1. 関数が呼び出される
    getContactDataSync()関数は、データ取得が必要になったときに呼ばれます。ここで実行が開始されます。

  2. 変数contactIdにデータIDを指定
    顧客データを取得するためのID(例:"some-contact-id")がcontactIdに代入されます。このIDを使って、特定の顧客データをサーバーから引き出します。

  3. Xrm.WebApi.retrieveRecordメソッドでデータを取得
    Xrm.WebApi.retrieveRecord("contact", contactId, "?$select=fullname,email,phone");を使ってサーバーからデータを取得します。ここで、「contact」テーブルから、contactIdで指定した顧客の「名前」「メールアドレス」「電話番号」が取り出されます。

    • ただし、同期的に動作するため、このデータ取得が完了するまで他の処理ができなくなります
  4. データの表示
    データが取得できたら、それぞれのフィールド(「名前」「メールアドレス」「電話番号」)をコンソールに出力します。この処理が終わるまで、他の操作は止まったままです。

データの流れ

  • まずcontactIdを通じて特定のデータが指定されます。
  • 次に、Xrm.WebApi.retrieveRecordメソッドがデータベースからこのデータを取得し、結果がresultに返されます。
  • 最後に、取得したデータがコンソールに出力され、処理が終了します。

非同期処理の流れ:getContactDataAsync()

次に、非同期処理を使った方法を見ていきましょう。非同期処理を使うことで、データの取得が終わる前でも他の操作が可能になります。

処理の流れ

  1. 関数が呼び出される
    getContactDataAsync()関数が呼ばれると、この非同期処理が開始されます。最初に変数の設定や準備を行います。

  2. 変数contactIdにデータIDを指定
    同期処理と同じように、取得したいデータのID(例:"some-contact-id")を変数contactIdに代入します。

  3. awaitでデータ取得を待つ
    次に、await Xrm.WebApi.retrieveRecord("contact", contactId, "?$select=fullname,email,phone");と書かれた行に進みます。このawaitは、データの取得が終わるのを待つことを指示しています。

    • awaitの処理は、データ取得中も他の操作が可能であることを意味しています。つまり、このデータ取得が終わるまで他の処理を中断する必要がありません。
  4. エラー処理
    try-catchブロックでエラー処理も追加しています。データ取得がうまくいかなかった場合、catchでエラーメッセージが表示されます。

  5. データの表示
    データが無事に取得できた場合、各フィールドの内容(「名前」「メールアドレス」「電話番号」)をコンソールに出力します。同期処理と異なり、この間にユーザーが他の操作を行っても問題がありません。

データの流れ

  • 同期処理と同じく、contactIdでデータが指定されます。
  • Xrm.WebApi.retrieveRecordメソッドがサーバーにリクエストを送信し、データがresultに返されます。
  • 結果のデータがコンソールに出力され、処理が終了します。

同期処理と非同期処理の違い

ここで、同期処理と非同期処理の違いを簡単にまとめます。

項目 同期処理 非同期処理
処理中の操作 他の操作が止まる 他の操作が可能
処理の待機 取得が終わるまで待機 処理中も他の作業が可能
ユーザー体験 操作が中断される 操作がスムーズ

子どもにもわかる例え話

非同期処理を、図書館で本を借りる例にたとえてみましょう。

  • 同期処理の場合:本を借りるために図書館に行き、受付で自分の順番が来るまで待ちます。前の人がたくさんの本を借りていると、自分が本を借りられるまで時間がかかってしまいます。
  • 非同期処理の場合:図書館に行って本を借りる手続きを始めますが、受付に「準備ができたら連絡してください」とお願いし、自分はほかのことをして待ちます。そうすれば、手続きが完了するまで、時間を無駄にしません。

コードの解説

最後に、コードの中で使われたメソッドについても詳しく説明します。

  1. Xrm.WebApi.retrieveRecord
    このメソッドは、特定のIDを持つレコードをデータベースから取得するために使います。取得したいテーブルの名前とID、そして取り出したいフィールド(例:fullname, email, phone)を指定します。

    • 構文: Xrm.WebApi.retrieveRecord("テーブル名", レコードID, "取得フィールド");
  2. async/await
    async関数内で使えるawaitは、特定の非同期処理が終わるまで待機するよう指示するキーワードです。awaitが使えるのは、必ずasync関数内です。非同期処理が完了したら次の処理に進みます。

  3. try-catchブロック
    try-catchは、エラー処理を行うための構文です。例えば、データ取得がうまくいかなかったとき、catch内でエラー内容をログに表示させることができます。これにより、エラーが発生してもアプリ全体が停止するのを防ぎます。


まとめ

非同期処理を使うことで、データ取得中も他の操作が可能となり、アプリがスムーズに動作するようになります。このコードの理解を通して、PowerAppsモデル駆動型アプリの開発がより効果的に進むことでしょう。

実践問題

問題: 以下の条件を満たす関数getAccountDataを作成してください。

  • 条件: 顧客データを非同期で取得し、取得したデータをコンソールに表示。

この記事では、PowerAppsモデル駆動型アプリでの非同期データ取得の活用について解説しました。非同期処理を理解して、アプリのパフォーマンスを向上させましょう。

ロールアップの使い方

ロールアップフィールドを使用して、関連データを集計する

  • 概要: 請求先テーブルを使用して、ロールアップフィールドの作成と活用方法を学びます。
  • 目的: 関連する子レコードのデータを親レコードで集計する方法を理解する。
  • : 各請求先(顧客)の全請求書の合計金額を算出する。

■結果

子レコード

 


手順:

  1. テーブルの確認と準備

    • 親テーブル: 請求先(スキーマ名:account
    • 子テーブル: 請求書(スキーマ名:invoice
    • 関連付け: 請求書の顧客フィールド(customerid)が請求先と関連付けられていることを確認。
  2. ロールアップフィールドの作成

    • 対象テーブル: 請求先(account

    • フィールドの追加:

      • 表示名: 合計請求金額
      • スキーマ: new_totalinvoiceamount
      • データ型: ロールアップ(通貨)
  3. ロールアップフィールドの設定

    • ロールアップの定義:

      • ソーステーブル: 請求書(invoice
      • 関連フィールド: 顧客customerid
      • 集計関数: 合計(Sum)
      • 対象フィールド: 請求書の合計金額totalamount
    • フィルターの設定(必要に応じて):

      • 例: 請求書のステータスが支払済みのものだけを集計
  4. フィールドの保存と公開

    • ロールアップフィールドの設定を保存し、カスタマイズを公開する。
  5. フォームへのフィールド追加

    • フォーム: 請求先メインフォーム
    • フィールド配置: 合計請求金額new_totalinvoiceamount)をフォーム上にドラッグ&ドロップ。
  6. 動作確認

    • 請求先レコードを開き、合計請求金額が正しく表示されていることを確認。
    • 注意: ロールアップフィールドの値は即時更新されない場合があります。必要に応じてフィールド横の再計算ボタンを使用。

データ型=通貨(計算)の例

実装例:フォーム上で入力値に基づいて他のフィールドを自動計算する

シナリオ
合計金額」と「消費税率」を入力すると、「税込金額」が自動計算される機能を実装します。

■結果(保存押下後)

■値設定前のフォーム

手順:

  1. テーブル(エンティティ)の作成または編集

    • テーブル名請求書スキーマ名:new_invoice
  2. フィールドの追加

    • 合計金額

      • 表示名合計金額
      • スキーマnew_totalamount
      • データ型通貨
    • 消費税率

      • 表示名消費税率
      • スキーマnew_taxrate
      • データ型小数
      • 精度2(小数点以下2桁まで)
    • 税込金額

      • 表示名税込金額
      • スキーマnew_totalincludingtax
      • データ型計算(通貨)
  3. 「税込金額」フィールドの計算式を設定

    • 「税込金額」フィールドの設定で、フィールドの種類を「計算」に変更。

    • 計算式の編集画面で、以下の数式を設定:

      = new_totalamount * (1 + new_taxrate)
  4. フォームの編集

    • フォーム請求書メインフォームを開く。

    • フィールド配置

      • 「合計金額」(new_totalamount
      • 「消費税率」(new_taxrate
      • 「税込金額」(new_totalincludingtax
    • これらのフィールドをフォーム上にドラッグ&ドロップ。

  5. 動作確認

    • アプリを起動し、新しい請求書レコードを作成。
    • 「合計金額」と「消費税率」を入力。
    • 「税込金額」が自動的に計算・表示されることを確認。