次の方法で共有


チュートリアル: ASP.NET MVC アプリの EF で、非同期プロシージャとストアド プロシージャを使用する

前のチュートリアルでは、同期プログラミング モデルを使用してデータの読み取りと更新を行う方法を学習しました。 このチュートリアルでは、非同期プログラミング モデルを実装する方法について説明します。 非同期コードは、サーバー リソースをより適切に使用するため、アプリケーションのパフォーマンスを向上させることができます。

このチュートリアルでは、エンティティに対する挿入、更新、および削除操作にストアド プロシージャを使用する方法についても説明します。

最後に、初回デプロイ後に実装したすべてのデータベース変更と共に、アプリケーションを Azure に再デプロイします。

以下の図は、使用するページの一部を示しています。

[部署] ページ

部署の作成

このチュートリアルでは、次の作業を行いました。

  • 非同期コードについて学習する
  • Department コントローラーを作成する
  • ストアド プロシージャの使用
  • Azure に展開する

前提条件

非同期コードを使用する理由

Web サーバーでは、利用できるスレッド数に限りがあります。負荷が高い状況では、利用できるスレッドが全部使われる可能性があります。 その場合、スレッドが解放されるまでサーバーは新しい要求を処理できません。 同期コードの場合、たくさんのスレッドが関連付けられていても、I/O の完了を待っているため、実際には何の作業も行っていないということがあります。 非同期コードの場合、あるプロセスが I/O の完了を待っているとき、そのスレッドは解放され、サーバーによって他の要求の処理に利用できます。 結果として、非同期コードは、サーバー リソースをより効率的に利用でき、サーバーは、より多くのトラフィックを遅延なく処理できます。

以前のバージョンの .NET では、非同期コードの記述とテストは複雑で、エラーが発生しやすく、デバッグが困難でした。 .NET 4.5 では、非同期コードの記述、テスト、デバッグが非常に簡単であるため、そうしない理由がない限り、通常は非同期コードを記述してください。 非同期コードは少量のオーバーヘッドを発生させますが、トラフィックが少ない場合、パフォーマンスに与える影響は無視して構わない程度です。またトラフィックが多い場合、相当なパフォーマンス改善が見込まれます。

非同期プログラミングの詳細については、.NET 4.5 の非同期サポートを使い、呼び出しのブロッキングを回避するに関する記事をご覧ください。

Department コントローラーを作成する

以前のコントローラーと同じ方法で Department コントローラーを作成します。ただし、今回は [非同期コントローラー アクションの使用] チェックボックスを選択します。

次の強調表示は、非同期にするために Index メソッドの同期コードに追加された内容を示しています。

public async Task<ActionResult> Index()
{
    var departments = db.Departments.Include(d => d.Administrator);
    return View(await departments.ToListAsync());
}

Entity Framework データベース クエリを非同期的に実行できるようにするために、次の 4 つの変更が適用されました。

  • このメソッドは async キーワードでマークされます。このキーワードは、メソッド本体の一部にコールバックを生成し、返される Task<ActionResult> オブジェクトを自動的に生成するようにコンパイラに指示します。
  • 戻り値の型が ActionResult から Task<ActionResult> に変更されました。 戻り値の Task<T> 型は、進行中の作業と型 T の結果を表します。
  • await キーワードが Web サービス呼び出しに適用されました。 コンパイラがこのキーワードを認識すると、裏でメソッドを 2 つの部分に分割します。 最初の部分は、非同期で開始される操作で終わります。 2 つ目の部分は、操作の完了時に呼び出されるコールバック メソッドに入ります。
  • 非同期バージョンの ToList 拡張メソッドが呼び出されました。

departments.ToList ステートメントは変更されますが、departments = db.Departments ステートメントは変更されないのはなぜですか? 理由は、クエリやコマンドをデータベースに送信するステートメントのみが非同期で実行されるためです。 departments = db.Departments ステートメントはクエリを設定しますが、ToList メソッドが呼び出されるまでクエリは実行されません。 そのため、ToList メソッドのみが非同期的に実行されます。

Details メソッドと HttpGet Edit メソッドと Delete メソッドでは、Find メソッドはクエリをデータベースに送信するメソッドであるため、非同期的に実行されるメソッドです。

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Department department = await db.Departments.FindAsync(id);
    if (department == null)
    {
        return HttpNotFound();
    }
    return View(department);
}

CreateHttpPost EditDeleteConfirmed メソッドでは、コマンドを実行させるのは SaveChanges メソッド呼び出しであり、メモリ上のエンティティを変更させるだけの db.Departments.Add(department) のようなステートメントではありません。

public async Task<ActionResult> Create(Department department)
{
    if (ModelState.IsValid)
    {
        db.Departments.Add(department);
    await db.SaveChangesAsync();
        return RedirectToAction("Index");
    }

Views\Department\Index.cshtml を開いて、テンプレート コードを次のコードに置き換えます。

@model IEnumerable<ContosoUniversity.Models.Department>
@{
    ViewBag.Title = "Departments";
}
<h2>Departments</h2>
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Budget)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.StartDate)
        </th>
    <th>
            Administrator
        </th>
        <th></th>
    </tr>
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Budget)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.StartDate)
        </td>
    <td>
            @Html.DisplayFor(modelItem => item.Administrator.FullName)
            </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.DepartmentID }) |
            @Html.ActionLink("Details", "Details", new { id=item.DepartmentID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.DepartmentID })
        </td>
    </tr>
}
</table>

このコードは、タイトルを Index から Departments に変更し、Administrator name を右に移動し、管理者の完全な名前を表示します。

Create、Delete、Details、および Edit ビューで、Course ビューで department name フィールドを "Department" に変更したのと同じように、InstructorID フィールドのキャプションを "Administrator" に変更します。

Create ビューと Edit ビューでは、次のコードを使用します。

<label class="control-label col-md-2" for="InstructorID">Administrator</label>

Delete ビューと Details ビューでは、次のコードを使用します。

<dt>
    Administrator
</dt>

アプリケーションを実行して、[Departments] タブをクリックします。

すべてが他のコントローラーと同じように動作しますが、このコントローラーでは、すべての SQL クエリが非同期的に実行されます。

Entity Framework で非同期コードを使用する場合の注意すべき点:

  • 非同期コードはスレッド セーフではありません。 つまり、同じコンテキスト インスタンスを使用する複数の操作を並列で実行しないでください。
  • 非同期コードのパフォーマンス上の利点を最大限に活用する場合、(ページングなどのために) ライブラリ パッケージを利用しているのであれば、それがクエリをデータベースに送信させる Entity Framework メソッドを呼び出す場合、非同期を利用する必要があります。

ストアド プロシージャの使用

一部の開発者や DBA は、データベース アクセスにストアド プロシージャを使用することを好みます。 Entity Framework の以前のバージョンでは、生の SQL クエリを実行することで、ストアド プロシージャを使用してデータを取得することができますが、更新操作にストアド プロシージャを使用するよう EF に指示することはできません。 EF 6 では、ストアド プロシージャを使用するよう Code First を簡単に構成できます。

  1. DAL\SchoolContext.cs で、強調表示されたコードを OnModelCreating メソッドに追加します。

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Entity<Course>()
            .HasMany(c => c.Instructors).WithMany(i => i.Courses)
            .Map(t => t.MapLeftKey("CourseID")
                .MapRightKey("InstructorID")
                .ToTable("CourseInstructor"));
        modelBuilder.Entity<Department>().MapToStoredProcedures();
    }
    

    このコードは、Department エンティティに対する挿入、更新、および削除操作に、ストアド プロシージャを使用するよう Entity Framework に指示します。

  2. パッケージ マネージャー コンソールで、次のコマンドを入力します。

    add-migration DepartmentSP

    Migrations\<timestamp>_DepartmentSP.cs を開き、挿入、更新、削除ストアド プロシージャを作成する Up メソッドのコードを確認します。

    public override void Up()
    {
        CreateStoredProcedure(
            "dbo.Department_Insert",
            p => new
                {
                    Name = p.String(maxLength: 50),
                    Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"),
                    StartDate = p.DateTime(),
                    InstructorID = p.Int(),
                },
            body:
                @"INSERT [dbo].[Department]([Name], [Budget], [StartDate], [InstructorID])
                  VALUES (@Name, @Budget, @StartDate, @InstructorID)
                  
                  DECLARE @DepartmentID int
                  SELECT @DepartmentID = [DepartmentID]
                  FROM [dbo].[Department]
                  WHERE @@ROWCOUNT > 0 AND [DepartmentID] = scope_identity()
                  
                  SELECT t0.[DepartmentID]
                  FROM [dbo].[Department] AS t0
                  WHERE @@ROWCOUNT > 0 AND t0.[DepartmentID] = @DepartmentID"
        );
        
        CreateStoredProcedure(
            "dbo.Department_Update",
            p => new
                {
                    DepartmentID = p.Int(),
                    Name = p.String(maxLength: 50),
                    Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"),
                    StartDate = p.DateTime(),
                    InstructorID = p.Int(),
                },
            body:
                @"UPDATE [dbo].[Department]
                  SET [Name] = @Name, [Budget] = @Budget, [StartDate] = @StartDate, [InstructorID] = @InstructorID
                  WHERE ([DepartmentID] = @DepartmentID)"
        );
        
        CreateStoredProcedure(
            "dbo.Department_Delete",
            p => new
                {
                    DepartmentID = p.Int(),
                },
            body:
                @"DELETE [dbo].[Department]
                  WHERE ([DepartmentID] = @DepartmentID)"
        );    
    }
    
  3. パッケージ マネージャー コンソールで、次のコマンドを入力します。

    update-database

  4. デバッグ モードでアプリケーションを実行し、[Departments] タブをクリックし、[新規作成] をクリックします。

  5. 新しい部署のデータを入力し、[作成] をクリックします。

  6. Visual Studio で、[出力] ウィンドウのログを参照し、ストアド プロシージャで新しい Department 行が挿入されたことを確認します。

    Department Insert SP

Code First では、既定のストアド プロシージャ名が作成されます。 既存のデータベースを使用している場合は、データベースに既に定義されているストアド プロシージャを使用するために、ストアド プロシージャ名のカスタマイズが必要になる場合があります。 その方法については、Entity Framework Code First での挿入、更新、削除にストアド プロシージャを使用するに関する記事をご覧ください。

生成されたストアド プロシージャの処理をカスタマイズする場合は、ストアド プロシージャを作成する雛形コードの移行 Up メソッドを編集できます。 そうすることで、移行が実行されるたびに変更が反映されます。この変更は、デプロイ後に運用環境で移行が自動的に実行される際に運用データベースに適用されます。

以前の移行で作成された既存のストアド プロシージャを変更する場合は、Add-Migration コマンドで空の移行を生成し、AlterStoredProcedure メソッドを呼び出すコードを手動で記述できます。

Azure に展開する

このセクションでは、このシリーズの移行と展開チュートリアルのオプション、Azure へのアプリのデプロイ セクションを完了している必要があります。 ローカル プロジェクト内のデータベースを削除して解決した移行エラーがある場合は、このセクションをスキップしてください。

  1. Visual Studio のソリューション エクスプローラーで、プロジェクトを右クリックし、コンテキスト メニューの [発行] をクリックします。

  2. [発行] をクリックします。

    Visual Studio によってアプリケーションが Azure にデプロイされ、Azure で実行されている既定のブラウザーでアプリケーションが開きます。

  3. アプリケーションをテストし、動作を確認します。

    データベースにアクセスするページを初めて実行すると、Entity Framework は、データベースを現在のデータ モデルに対応させるために必要な移行 Up メソッドをすべて実行します。 これで、このチュートリアルで追加した Department ページを含め、前回のデプロイ以降に追加したすべての Web ページを使用できるようになりました。

コードを取得する

完成したプロジェクトをダウンロードする

その他のリソース

他の Entity Framework リソースへのリンクは、「ASP.NET データ アクセス - 推奨リソース」にあります。

次のステップ

このチュートリアルでは、次の作業を行いました。

  • 非同期コードについて学習しました
  • Department コントローラーを作成しました
  • ストアド プロシージャを使用しました
  • Azure にデプロイしました

複数のユーザーが同じエンティティを同時に更新する場合の競合の処理方法については、次の記事に進みます。