次の方法で共有


方法: コード アプリを Azure SQL に接続する (プレビュー)

このガイドでは、Azure SQL Database を設定し、Power SDK を使用して Power Apps コード アプリに接続する方法について説明します。

プレビュー機能は運用環境での使用を想定しておらず、機能が制限されている可能性があります。 これらの機能は、お客様が早期にアクセスしてフィードバックを提供できるように、公式リリースの前に利用できます。

このガイドでは、次の内容について説明します。

  • Azure SQL Server とデータベースのプロビジョニング
  • SQL テーブルとストアド プロシージャの作成
  • Power SDK を使用して Power Apps コード アプリに接続する

[前提条件]

Azure SQL Server とデータベースを設定する

  1. [SQL デプロイ オプションの選択] に移動する - Microsoft Azure
  2. SQL データベースの選択 -> リソースの種類: 単一データベース ->Create
  3. 書き込む:
    • [リソース グループ]: [ 新規作成 ] を選択し、リソース グループ名を入力します (例: rg-codeapps-dev
    • 表示名: sqldb-codeapps-dev
    • サーバー: [ 新規作成 ] と [入力] を選択します。
      • サーバー名: sql-codeapps-dev
      • 場所: Power Platform 環境に最も近いリージョンを選択します。
      • 認証方法: Microsoft Entra 専用認証を使用する
      • Microsoft Entra 管理者の設定: [ 管理者の設定] を選択し、 独自のユーザーを選択します
    • OKを選択します。
  4. コンピューティング + ストレージ: General Purpose - サーバーレス
  5. 次: ネットワーク を選択します
  6. 書き込む:
    • 接続方法: パブリック エンドポイント
    • Azure サービスとリソースにこのサーバーへのアクセスを許可する: はい
    • 現在のクライアント IP アドレスを追加する: はい
  7. [確認と作成] ->Create を選択する
  8. デプロイが完了するまで待ってから、[リソースに移動] を選択します

サンプル データをデプロイする

  1. Visual Studio Code 内で、[ 拡張機能 ] を選択します (Ctrl + Shift + X)

  2. アクティビティ バーで SQL Server (mssql) 拡張機能を見つけて開くか、Ctrl + Alt + D キーを使用します

  3. [接続] で、[+ 接続の追加] を選択します。

    VS Code SQL Server 拡張機能での接続の追加

  4. [ データベースへの接続 ] ダイアログで、[ Azure の参照] を選択し、サブスクリプション、リソース グループ (例: rg-codeapps-dev)、サーバー (例: sql-codeapps-dev)、データベース ( sqldb-codeapps-dev など) を選択します。

  5. [認証の種類] で、[Microsoft Entra ID - MFA サポート付きユニバーサル] を選択します

  6. ブラウザーで Azure portal が開かれていることを確認し、[ サインイン] を選択します。 サインインを求めるメッセージが表示され、次の情報が表示されます。

    SQL 接続の Microsoft Entra サインイン プロンプト

  7. [接続] を選択する

    VS Code で Azure SQL データベースに接続されている

  8. [SQL SERVER] パネルで、データベースを右クリックし、[新しいクエリ] を選択します

    VS Code SQL 拡張機能のデータベースの新しいクエリ コマンド

  9. 新しいクエリ ウィンドウに、次の SQL を貼り付けます。

    -- Drop existing objects if they exist
    IF OBJECT_ID('dbo.Projects', 'U') IS NOT NULL DROP TABLE dbo.Projects;
    
    -- =============================================
    -- CREATE TABLES
    -- =============================================
    
    -- Projects Table
    CREATE TABLE [dbo].[Projects](
        [ProjectId] [int] IDENTITY(1,1) NOT NULL,
        [Name] [nvarchar](255) NOT NULL,
        [Description] [nvarchar](max) NULL,
        [StartDate] [date] NULL,
        [EndDate] [date] NULL,
        [Status] [nvarchar](50) NOT NULL DEFAULT ('Planning'),
        [Priority] [nvarchar](20) NOT NULL DEFAULT ('Medium'),
        [Budget] [decimal](18, 2) NULL,
        [ProjectManagerEmail] [nvarchar](255) NOT NULL,
        [CreatedBy] [nvarchar](255) NOT NULL,
        [CreatedDate] [datetime2](7) NOT NULL DEFAULT (getutcdate()),
        [IsActive] [bit] NOT NULL DEFAULT (1),
        CONSTRAINT [PK_Projects] PRIMARY KEY ([ProjectId])
    );
    GO
    
    -- =============================================
    -- ADD CONSTRAINTS
    -- =============================================
    
    -- Project Status Check
    ALTER TABLE [dbo].[Projects] ADD CONSTRAINT [CK_Projects_Status] 
    CHECK ([Status] IN ('Planning', 'Active', 'On Hold', 'Completed', 'Cancelled'));
    
    -- Project Priority Check
    ALTER TABLE [dbo].[Projects] ADD CONSTRAINT [CK_Projects_Priority] 
    CHECK ([Priority] IN ('Low', 'Medium', 'High', 'Critical'));
    GO
    
    -- =============================================
    -- STORED PROCEDURES
    -- =============================================
    
    -- Get All Projects
    IF OBJECT_ID('dbo.GetAllProjects', 'P') IS NOT NULL DROP PROCEDURE dbo.GetAllProjects;
    GO
    CREATE PROCEDURE [dbo].[GetAllProjects]
    AS
    BEGIN
        SET NOCOUNT ON;
    
        SELECT 
            [ProjectId], [Name], [Description], [StartDate], [EndDate],
            [Status], [Priority], [Budget], [ProjectManagerEmail],
            [CreatedBy], [CreatedDate], [IsActive]
        FROM [dbo].[Projects]
        WHERE [IsActive] = 1
        ORDER BY [CreatedDate] DESC;
    END
    GO
    
    -- Create Project
    IF OBJECT_ID('dbo.CreateProject', 'P') IS NOT NULL DROP PROCEDURE dbo.CreateProject;
    GO
    CREATE PROCEDURE [dbo].[CreateProject]
        @Name NVARCHAR(255),
        @Description NVARCHAR(MAX) = NULL,
        @StartDate DATE = NULL,
        @EndDate DATE = NULL,
        @Status NVARCHAR(50) = 'Planning',
        @Priority NVARCHAR(20) = 'Medium',
        @Budget DECIMAL(18,2) = NULL,
        @ProjectManagerEmail NVARCHAR(255),
        @CreatedBy NVARCHAR(255)
    AS
    BEGIN
        SET NOCOUNT ON;
    
        INSERT INTO [dbo].[Projects] (
            [Name], [Description], [StartDate], [EndDate], 
            [Status], [Priority], [Budget], [ProjectManagerEmail], [CreatedBy]
        )
        VALUES (
            @Name, @Description, @StartDate, @EndDate,
            @Status, @Priority, @Budget, @ProjectManagerEmail, @CreatedBy
        );
    
        SELECT SCOPE_IDENTITY() as ProjectId;
    END
    GO
    
    -- Update Project
    IF OBJECT_ID('dbo.UpdateProject', 'P') IS NOT NULL DROP PROCEDURE dbo.UpdateProject;
    GO
    CREATE PROCEDURE [dbo].[UpdateProject]
        @ProjectId INT,
        @Name NVARCHAR(255) = NULL,
        @Description NVARCHAR(MAX) = NULL,
        @StartDate DATE = NULL,
        @EndDate DATE = NULL,
        @Status NVARCHAR(50) = NULL,
        @Priority NVARCHAR(20) = NULL,
        @Budget DECIMAL(18,2) = NULL,
        @ProjectManagerEmail NVARCHAR(255) = NULL
    AS
    BEGIN
        SET NOCOUNT ON;
    
        UPDATE [dbo].[Projects]
        SET 
            [Name] = ISNULL(@Name, [Name]),
            [Description] = ISNULL(@Description, [Description]),
            [StartDate] = ISNULL(@StartDate, [StartDate]),
            [EndDate] = ISNULL(@EndDate, [EndDate]),
            [Status] = ISNULL(@Status, [Status]),
            [Priority] = ISNULL(@Priority, [Priority]),
            [Budget] = ISNULL(@Budget, [Budget]),
            [ProjectManagerEmail] = ISNULL(@ProjectManagerEmail, [ProjectManagerEmail])
        WHERE [ProjectId] = @ProjectId AND [IsActive] = 1;
    
        SELECT @@ROWCOUNT as RowsAffected;
    END
    GO
    
    -- Delete Project (Soft Delete)
    IF OBJECT_ID('dbo.DeleteProject', 'P') IS NOT NULL DROP PROCEDURE dbo.DeleteProject;
    GO
    CREATE PROCEDURE [dbo].[DeleteProject]
        @ProjectId INT
    AS
    BEGIN
        SET NOCOUNT ON;
    
        UPDATE [dbo].[Projects]
        SET [IsActive] = 0
        WHERE [ProjectId] = @ProjectId AND [IsActive] = 1;
    
        SELECT @@ROWCOUNT as RowsAffected;
    END
    GO
    
    -- =============================================
    -- SAMPLE DATA
    -- =============================================
    
    -- Insert Sample Projects
    INSERT INTO [dbo].[Projects] ([Name], [Description], [StartDate], [EndDate], [Status], [Priority], [Budget], [ProjectManagerEmail], [CreatedBy]) VALUES
    ('Website Redesign', 'Complete redesign of company website with modern UI/UX', '2025-06-01', '2025-08-31', 'Active', 'High', 75000.00, 'sarah.johnson@company.com', 'admin@company.com'),
    ('Mobile App Development', 'Develop iOS and Android mobile application for customer portal', '2025-07-01', '2025-12-31', 'Planning', 'Critical', 150000.00, 'mike.chen@company.com', 'admin@company.com'),
    ('Database Migration', 'Migrate legacy database to cloud infrastructure', '2025-05-15', '2025-09-30', 'Active', 'Medium', 50000.00, 'lisa.williams@company.com', 'admin@company.com');
    GO
    
    PRINT 'Projects-only database schema created successfully with sample data!';
    
  10. 緑色の再生アイコン (Ctrl + Shift + E) を選択して クエリを実行します

  11. QUERY RESULTS の出力にエラーは表示されません。

あなたのコードアプリを初期化する

まだ作成していない場合は、「ゼロからアプリを作成する」の手順に従って、コード アプリを作成または初期化します。

Power Platform で SQL Server 接続を作成する

  1. Power Apps を開く

  2. 環境を選択する

  3. [ 接続] に移動します。 その他のメニュー 内にある可能性があります。

  4. [+ 新しい接続] を選択する

    + Power Apps で新規接続

  5. SQL Server の選択

  6. 認証の種類の選択: Microsoft Entra ID Integrated

  7. ポップアップ認証プロンプトで [ 作成 とサインイン] を選択する

SQL テーブル接続をアプリに追加する

  1. 環境内で使用可能な接続を一覧表示します。 作成した接続が表示されます。

    pac connection list
    

    次のような一覧が表示されます。

    SQL 接続を示す Power Platform 接続の一覧

  2. プロジェクト テーブルをプロジェクトに追加するには、接続 ID (最初の列) をコピーし、次のコマンドを使用します。

    pac code add-data-source -a "shared_sql" -c "[CONNECTION ID]"  -d "[SQL SERVER NAME].database.windows.net,[DATA BASE NAME]" -sp "dbo.GetAllProjects"
    

    例えば次が挙げられます。

    pac code add-data-source -a "shared_sql" -c "aaaa0000bb11222233cc444444dddddd"  -d "sql-codeapps-dev.database.windows.net,sqldb-codeapps-dev" -sp "dbo.GetAllProjects"
    
  3. ServicesModels フォルダーを開き、新しく生成されたコードを確認します。

プロジェクトのテーブルを追加する

  1. Fluent UI を使用してプロジェクトのテーブルを表示するため、React 18 にダウングレードし、次を使用してインストールします。

    npm install react@^18.0.0 react-dom@^18.0.0 @types/react@^18.0.0 @types/react-dom@^18.0.0
    npm install @fluentui/react-components
    
  2. 次のコードを使用して、srcという名前ProjectsTable.tsx新しいファイルを追加します。

    /**
     * ProjectsTable Component - Displays project data from Power Platform in a sortable DataGrid
     */
    import React, { useEffect, useState, useCallback, useMemo } from 'react';
    import {
      DataGrid,
      DataGridHeader,
      DataGridRow,
      DataGridHeaderCell,
      DataGridCell,
      DataGridBody,
      TableColumnDefinition,
      TableRowId,
      Spinner,
      MessageBar,
      Badge,
      makeStyles,
      tokens,
    } from '@fluentui/react-components';
    import { GetAllProjectsService } from './Services/GetAllProjectsService';
    
    // String formatting utility for localizable messages
    const formatMessage = (template: string, params: Record<string, string | number> = {}): string => {
      return template.replace(/\{(\w+)\}/g, (match, key) => {
        const value = params[key];
        return value !== undefined ? String(value) : match;
      });
    };
    
    // Common UI messages
    const MESSAGE_STRINGS = {
      LOADING: 'Loading data...',
      NO_DATA: 'No data found.',
      GENERIC_ERROR: 'An unexpected error occurred',
      LOAD_ERROR: 'Failed to load data. Please try again.',
      PROJECT_COUNTER_SINGLE: 'Showing {count} project',
      PROJECT_COUNTER_PLURAL: 'Showing {count} projects',
      COLUMN_PROJECT_NAME: 'Project Name',
      COLUMN_DESCRIPTION: 'Description',
      COLUMN_START_DATE: 'Start Date',
      COLUMN_END_DATE: 'End Date',
      COLUMN_STATUS: 'Status',
      COLUMN_PRIORITY: 'Priority',
      ARIA_LABEL_DATA_GRID: 'Projects data grid',
    } as const;
    
    // Project data type
    type ProjectItem = {
      ProjectId?: number;
      Name?: string;
      Description?: string;
      StartDate?: string;
      EndDate?: string;
      Status?: string;
      Priority?: string;
      Budget?: number;
      ProjectManagerEmail?: string;
      CreatedBy?: string;
      CreatedDate?: string;
      IsActive?: boolean;
    };
    
    // DataGrid columns
    const COLUMNS: TableColumnDefinition<ProjectItem>[] = [
      {
        columnId: 'name',
        compare: (a, b) => (a.Name || '').localeCompare(b.Name || ''),
        renderHeaderCell: () => MESSAGE_STRINGS.COLUMN_PROJECT_NAME,
        renderCell: (item) => item.Name || '',
      },
      {
        columnId: 'description',
        compare: (a, b) => (a.Description || '').localeCompare(b.Description || ''),
        renderHeaderCell: () => MESSAGE_STRINGS.COLUMN_DESCRIPTION,
        renderCell: (item) => item.Description || '',
      },
      {
        columnId: 'startDate',
        compare: (a, b) => new Date(a.StartDate || '').getTime() - new Date(b.StartDate || '').getTime(),
        renderHeaderCell: () => MESSAGE_STRINGS.COLUMN_START_DATE,
        renderCell: (item) => item.StartDate ? new Date(item.StartDate).toLocaleDateString() : '',
      },
      {
        columnId: 'endDate',
        compare: (a, b) => new Date(a.EndDate || '').getTime() - new Date(b.EndDate || '').getTime(),
        renderHeaderCell: () => MESSAGE_STRINGS.COLUMN_END_DATE,
        renderCell: (item) => item.EndDate ? new Date(item.EndDate).toLocaleDateString() : '',
      }, {
        columnId: 'status',
        compare: (a, b) => (a.Status || '').localeCompare(b.Status || ''),
        renderHeaderCell: () => MESSAGE_STRINGS.COLUMN_STATUS,
        renderCell: (item) => <StatusBadge status={item.Status || ''} />,
      },
      {
        columnId: 'priority',
        compare: (a, b) => (a.Priority || '').localeCompare(b.Priority || ''),
        renderHeaderCell: () => MESSAGE_STRINGS.COLUMN_PRIORITY,
        renderCell: (item) => <PriorityBadge priority={item.Priority || ''} />,
      },
    ];
    
    // Row ID generator
    const getRowId = (item: ProjectItem): TableRowId =>
      item.ProjectId?.toString() || `temp-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
    
    // Extracts a user-friendly error message from various error types
    const extractErrorMessage = (
      error: unknown,
      fallbackMessage = MESSAGE_STRINGS.GENERIC_ERROR
    ): string => {
      if (error instanceof Error) {
        return error.message;
      }
      if (typeof error === 'string') {
        return error;
      } return fallbackMessage;
    };
    
    // Badge component for Priority
    const PriorityBadge: React.FC<{ priority: string }> = React.memo(({ priority }) => {
      const styles = useStyles();
      const badgeProps = useMemo(() => {
        const getPriorityAppearance = (priority: string) => {
          switch (priority?.toLowerCase()) {
            case 'critical':
              return { appearance: 'filled' as const, color: 'danger' as const };
            case 'high':
              return { appearance: 'filled' as const, color: 'important' as const };
            case 'medium':
              return { appearance: 'filled' as const, color: 'warning' as const };
            case 'low':
              return { appearance: 'filled' as const, color: 'success' as const };
            default:
              return { appearance: 'outline' as const, color: 'subtle' as const };
          }
        };
        return getPriorityAppearance(priority);
      }, [priority]);
    
      return (
        <Badge {...badgeProps} className={styles.badge}>
          {priority || 'Unknown'}
        </Badge>
      );
    });
    
    PriorityBadge.displayName = 'PriorityBadge';
    
    // Badge component for Status
    const StatusBadge: React.FC<{ status: string }> = React.memo(({ status }) => {
      const styles = useStyles();
      const badgeProps = useMemo(() => {
        const getStatusAppearance = (status: string) => {
          switch (status?.toLowerCase()) {
            case 'completed':
              return { appearance: 'filled' as const, color: 'success' as const };
            case 'active':
              return { appearance: 'filled' as const, color: 'brand' as const };
            case 'planning':
              return { appearance: 'filled' as const, color: 'informative' as const };
            case 'on hold':
              return { appearance: 'filled' as const, color: 'warning' as const };
            case 'cancelled':
              return { appearance: 'filled' as const, color: 'danger' as const };
            default:
              return { appearance: 'outline' as const, color: 'subtle' as const };
          }
        };
        return getStatusAppearance(status);
      }, [status]);
    
      return (
        <Badge {...badgeProps} className={styles.badge}>
          {status || 'Unknown'}
        </Badge>
      );
    });
    
    StatusBadge.displayName = 'StatusBadge';
    
    // Styles
    const useStyles = makeStyles({
      container: {
        padding: tokens.spacingVerticalXXL,
      },
      loadingContainer: {
        display: 'flex',
        alignItems: 'center',
        gap: tokens.spacingHorizontalS,
        padding: tokens.spacingVerticalXXL,
      },
      messageBar: {
        marginBottom: tokens.spacingVerticalXL,
      },
      projectCounter: {
        marginBottom: tokens.spacingVerticalM,
        fontSize: tokens.fontSizeBase200,
        color: tokens.colorNeutralForeground2,
      }, dataGrid: {
        width: '100%',
      },
      badge: {
        fontSize: tokens.fontSizeBase200,
        fontWeight: tokens.fontWeightMedium,
        textTransform: 'capitalize',
      },
    });
    
    // Custom hook to fetch and manage project data
    const useProjectsData = (): {
      projects: ProjectItem[];
      loading: boolean;
      error: string | null;
      refetch: () => Promise<void>;
    } => {
      const [projects, setProjects] = useState<ProjectItem[]>([]);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState<string | null>(null);
    
      const fetchProjects = useCallback(async () => {
        try {
          setLoading(true);
          setError(null); const result = await GetAllProjectsService.GetAllProjects();
          if (result.success && result.data?.ResultSets?.Table1) {
            const projectsData = Array.isArray(result.data.ResultSets.Table1)
              ? result.data.ResultSets.Table1 as ProjectItem[]
              : [result.data.ResultSets.Table1] as ProjectItem[];
            setProjects(projectsData);
          } else {
            const errorMsg = result.error instanceof Error
              ? result.error.message
              : result.error || MESSAGE_STRINGS.LOAD_ERROR;
            setError(errorMsg);
            console.error('Failed to fetch projects:', result.error);
          }
        } catch (error) {
          const errorMessage = extractErrorMessage(error, MESSAGE_STRINGS.GENERIC_ERROR);
          setError(errorMessage);
          console.error('Error fetching projects:', error);
        } finally {
          setLoading(false);
        }
      }, []);
    
      useEffect(() => {
        fetchProjects();
      }, [fetchProjects]);
    
      return { projects, loading, error, refetch: fetchProjects };
    };
    
    // UI Components
    const LoadingSpinner: React.FC = () => {
      const styles = useStyles();
      return (
        <div className={styles.loadingContainer}>
          <Spinner size="small" />
          <span>{MESSAGE_STRINGS.LOADING}</span>
        </div>
      );
    };
    
    const ErrorMessage: React.FC<{ error: string }> = ({ error }) => {
      const styles = useStyles();
      return (
        <MessageBar intent="error" className={styles.messageBar}>
          {error}
        </MessageBar>
      );
    };
    
    const EmptyState: React.FC = () => {
      const styles = useStyles();
      return (
        <MessageBar intent="info" className={styles.messageBar} style={{ textAlign: 'center' }}>
          {MESSAGE_STRINGS.NO_DATA}
        </MessageBar>
      );
    };
    
    const ProjectCounter: React.FC<{ count: number }> = ({ count }) => {
      const styles = useStyles();
    
      const counterMessage = useMemo(() => {
        return count === 1
          ? formatMessage(MESSAGE_STRINGS.PROJECT_COUNTER_SINGLE, { count })
          : formatMessage(MESSAGE_STRINGS.PROJECT_COUNTER_PLURAL, { count });
      }, [count]);
    
      return (
        <div className={styles.projectCounter}>
          {counterMessage}
        </div>
      );
    };
    
    // Main component
    const ProjectsTable: React.FC = () => {
      const styles = useStyles();
      const { projects, loading, error } = useProjectsData();
      const projectCount = useMemo(() => projects.length, [projects.length]);
      const memoizedProjects = useMemo(() => projects, [projects]);
      const dataGridProps = useMemo(() => ({
        items: memoizedProjects,
        columns: COLUMNS,
        sortable: true,
        getRowId,
        focusMode: "cell" as const,
        className: styles.dataGrid,
        "aria-label": MESSAGE_STRINGS.ARIA_LABEL_DATA_GRID,
      }), [memoizedProjects, styles.dataGrid]);
    
      if (loading) {
        return (
          <div className={styles.container}>
            <LoadingSpinner />
          </div>
        );
      }
    
      if (error) {
        return (
          <div className={styles.container}>
            <ErrorMessage error={error} />
          </div>
        );
      }
    
      if (projectCount === 0) {
        return (
          <div className={styles.container}>
            <EmptyState />
          </div>
        );
      }
      return (
        <div className={styles.container}>
          <ProjectCounter count={projectCount} />      <DataGrid {...dataGridProps}>
            <DataGridHeader>
              <DataGridRow>
                {({ renderHeaderCell }) => (
                  <DataGridHeaderCell>{renderHeaderCell()}</DataGridHeaderCell>
                )}
              </DataGridRow>
            </DataGridHeader>
            <DataGridBody<ProjectItem>>
              {({ item, rowId }) => (
                <DataGridRow<ProjectItem> key={rowId}>
                  {({ renderCell }) => (
                    <DataGridCell>{renderCell(item)}</DataGridCell>
                  )}
                </DataGridRow>
              )}
            </DataGridBody>
          </DataGrid>
        </div>
      );
    };
    
    export default React.memo(ProjectsTable);
    
    
  3. FluentProviderProjectsTablemaint.tsxを追加します。

     import { StrictMode } from 'react'
     import { createRoot } from 'react-dom/client'
     import './index.css'
     import PowerProvider from './PowerProvider.tsx'
     import { FluentProvider, webLightTheme } from '@fluentui/react-components'
     import ProjectsTable from './ProjectsTable.tsx'
    
     createRoot(document.getElementById('root')!).render(
       <StrictMode>
         <PowerProvider>
           <FluentProvider theme={webLightTheme}>
             <ProjectsTable />
           </FluentProvider>
         </PowerProvider>
       </StrictMode>,
     )
    
    
  4. 次を使用してアプリを実行します。

    npm run dev
    

    表示されたコマンド ウィンドウで、次のアプリ リンクを開きます。

    アプリの URL と状態を含む Power SDK サーバー コンソール

  5. アプリが開くと、同意ダイアログが表示され、[許可] を選択 します

    アプリのアクセス許可を要求する同意ダイアログ

  6. プロジェクトのデータ グリッドが表示されます。

    並べ替え可能な列とバッジを含むプロジェクト データ グリッド

Power Apps へのアプリの発行

  1. アプリの発行と共有の準備ができたら、 Ctrl + C キーを押して Vite サーバーが停止していることを確認してから、次の PowerShell を使用します。

    npm run build
    pac code push
    
  2. 提供されたリンクを使用してアプリを開いてテストします。

    Power Apps に、アプリを開くリンク付きで発行されたアプリ

トラブルシューティング

このセクションでは、Azure SQL Database で Power Apps コード アプリを設定するときに発生する可能性がある一般的な問題について説明します。

Azure SQL Database に関する問題

Azure SQL Database を使用すると、これらの問題が発生する可能性があります。

Azure SQL Database に接続できない

症状:

  • 接続のタイムアウトエラー
  • VS Code SQL 拡張機能から接続するときの認証エラー

ソリューション:

  1. ファイアウォール設定の確認:

    • Azure portal で、SQL Server に移動します
    • セキュリティネットワークに移動する
    • [Azure サービスとリソースにこのサーバーへのアクセスを許可する] が [はい] に設定されていることを確認します
    • 現在のクライアント IP アドレスをファイアウォール規則に追加する
  2. 認証方法の確認:

    • VS Code で MFA サポートを使用して Microsoft Entra ID - Universal を 使用していることを確認する
    • Azure portal と VS Code の両方で同じ Azure アカウントにサインインしていることを確認します
    • サインアウトして再びサインインして認証トークンを更新してみてください
  3. ネットワーク接続の確認:

    # Test connectivity to SQL Server
    Test-NetConnection -ComputerName "your-sql-server.database.windows.net" -Port 1433
    

SQL クエリの実行エラー

症状:

  • SQL スクリプトの実行時にアクセス許可が拒否されたエラー
  • オブジェクトが既に存在するエラー

ソリューション:

  1. アクセス許可の問題:

    • SQL Server の Microsoft Entra 管理者としてユーザー アカウントが設定されていることを確認する
    • データベースに対する db_owner または適切なアクセス許可があることを確認する
  2. オブジェクトが存在するエラー:

    • SQL スクリプトには DROP ステートメントが含まれています。これらは複数回実行しても安全です
    • 制約エラーが発生した場合は、最初に drop ステートメントを個別に実行します

Node.js と npm の問題

Node.js と npm を使用すると、これらの問題が発生する可能性があります。

ポート 3000 は既に使用されています

症状:

  • EADDRINUSE: アドレスは既に使用中です :::3000
  • Vite サーバーが起動しない

ソリューション:

  1. 既存のプロセスを強制終了する:

    # Find process using port 3000
    netstat -ano | findstr :3000
    # Kill the process (replace PID with actual process ID)
    taskkill /PID [PID] /F
    
  2. 代替ポートを使用する:

    • 別のポートを使用するには、vite.config.tsを更新してください。
    • それに応じて Power SDK の構成を更新する

パッケージのインストールエラー

症状:

  • npm のインストール エラー
  • モジュールが見つからないエラー

ソリューション:

  1. npm キャッシュのクリア:

    npm cache clean --force
    npm install
    
  2. node_modulesを削除して再インストールします。

    Remove-Item -Recurse -Force node_modules
    Remove-Item package-lock.json
    npm install
    
  3. ノード バージョンの問題:

    # Check Node version
    node --version
    # Should be LTS version (18.x or 20.x)
    

ランタイム接続エラー

症状:

  • React アプリの "データの読み込みに失敗しました"
  • 接続拒否エラー

ソリューション:

  1. Power Platform の接続を確認します。

    • Power Platform で SQL Server 接続が動作していることを確認する
    • Power Apps で接続をテストする
  2. 同意の問題:

    • アプリが最初に読み込まれるときに同意をするようにしてください。
    • ブラウザーのキャッシュをクリアしてやり直す
  3. 環境の不一致:

    • 接続が作成されたのと同じ環境でアプリを実行していることを確認する
    • ブラウザー プロファイルが Power Platform アカウントと一致することを確認する