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

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

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

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

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

[部署] ページ

部署の作成

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

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

前提条件

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

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

以前のバージョンの .NET では、非同期コードの記述とテストが複雑で、エラーが発生しやすく、デバッグが困難でした。 .NET 4.5 では、非同期コードの記述、テスト、デバッグが非常に簡単であるため、理由がない限り、通常は非同期コードを記述する必要があります。 非同期コードでは少量のオーバーヘッドが発生しますが、トラフィックが少ない状況ではパフォーマンスの低下はごくわずかですが、トラフィックが多い状況ではパフォーマンスが大幅に向上する可能性があります。

非同期プログラミングの詳細については、「 .NET 4.5 の非同期サポートを使用して呼び出しをブロックしないようにする」を参照してください。

部門コントローラーを作成する

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

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

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

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

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

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

Detailsメソッドと メソッドと DeleteHttpGetEdit メソッドでは、 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 Edit、、および DeleteConfirmed の各メソッドでは、メモリ内の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>

このコードでは、タイトルを [インデックス] から [部署] に変更し、管理者名を右側に移動し、管理者の完全な名前を指定します。

[作成]、[削除]、[詳細]、および [編集] ビューで、コース ビューで部門名フィールドを "Department" に変更したのと同じ方法で、フィールドのキャプションを "管理者" に変更InstructorIDします。

[作成] ビューと [編集] ビューでは、次のコードを使用します。

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

[削除] ビューと [詳細] ビューでは、次のコードを使用します。

<dt>
    Administrator
</dt>

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

すべてが他のコントローラーと同じように動作しますが、このコントローラーでは、すべての 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();
    }
    

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

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

    add-migration DepartmentSP

    Migrations\<timestamp>_DepartmentSP.cs を開き、Insert、Update、Delete ストアド プロシージャを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. デバッグ モードでアプリケーションを実行し、[ 部門 ] タブをクリックし、[ 新規作成] をクリックします。

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

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

    Department Insert SP

Code First は、既定のストアド プロシージャ名を作成します。 既存のデータベースを使用している場合は、データベースで既に定義されているストアド プロシージャを使用するために、ストアド プロシージャ名のカスタマイズが必要になる場合があります。 その方法については、「 Entity Framework Code First Insert/Update/Delete ストアド プロシージャ」を参照してください。

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

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

Azure にデプロイ

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

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

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

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

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

    データベースにアクセスするページを初めて実行すると、Entity Framework は、現在のデータ モデルでデータベースを最新の状態にするために必要なすべての移行 Up 方法を実行します。 このチュートリアルで追加した部門ページを含め、前回のデプロイ以降に追加したすべての Web ページを使用できるようになりました。

コードを取得する

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

その他のリソース

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

次の手順

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

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

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