次の方法で共有


チュートリアル: EF Core を使用した ASP.NET MVC の高度なシナリオについて学ぶ

前のチュートリアルでは、階層ごとのテーブルの継承を実装しました。 このチュートリアルでは、Entity Framework Core を使用する ASP.NET Core Web アプリケーションの開発の基本を超える場合に注意する必要があるいくつかのトピックについて説明します。

このチュートリアルでは、次の操作を行います。

  • 生の SQL クエリを実行する
  • クエリを呼び出してエンティティを返す
  • クエリを呼び出して他の型を返す
  • 更新クエリを呼び出す
  • SQL クエリを調べる
  • 抽象化レイヤーを作成する
  • 変更の自動検出について
  • ソース コードと開発計画 EF Core について説明します
  • 動的 LINQ を使用してコードを簡略化する方法について説明します

前提 条件

生の SQL クエリを実行する

Entity Framework を使用する利点の 1 つは、データを格納する特定の方法にコードを密接に関連付けすぎないようにすることです。 これを行うには、SQL クエリとコマンドを生成します。これにより、自分で記述する必要がなくなります。 ただし、手動で作成した特定の SQL クエリを実行する必要がある場合は、例外的なシナリオがあります。 これらのシナリオでは、Entity Framework Code First API には、SQL コマンドをデータベースに直接渡すメソッドが含まれています。 EF Core 1.0 には次のオプションがあります。

  • エンティティ型を返すクエリには、DbSet.FromSql メソッドを使用します。 返されるオブジェクトは、DbSet オブジェクトで想定される型である必要があります。また、の追跡をオフにしない限り、データベース コンテキストによって自動的に追跡されます。

  • クエリ以外のコマンドには、Database.ExecuteSqlCommand を使用します。

エンティティではない型を返すクエリを実行する必要がある場合は、EF によって提供されるデータベース接続で ADO.NET を使用できます。 このメソッドを使用してエンティティ型を取得した場合でも、返されるデータはデータベース コンテキストによって追跡されません。

Web アプリケーションで SQL コマンドを実行する場合も同様に、サイトを SQL インジェクション攻撃から保護するための予防措置を講じる必要があります。 これを行う 1 つの方法は、パラメーター化されたクエリを使用して、Web ページによって送信された文字列を SQL コマンドとして解釈できないようにすることです。 このチュートリアルでは、ユーザー入力をクエリに統合するときに、パラメーター化されたクエリを使用します。

クエリを呼び出してエンティティを返す

DbSet<TEntity> クラスは、TEntity型のエンティティを返すクエリを実行するために使用できるメソッドを提供します。 これがどのように機能するかを確認するには、Department コントローラーの Details メソッドのコードを変更します。

DepartmentsController.csでは、Details メソッドで、次の強調表示されたコードに示すように、部門を取得するコードを FromSql メソッド呼び出しに置き換えます。

public async Task<IActionResult> Details(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
    var department = await _context.Departments
        .FromSql(query, id)
        .Include(d => d.Administrator)
        .AsNoTracking()
        .FirstOrDefaultAsync();

    if (department == null)
    {
        return NotFound();
    }

    return View(department);
}

新しいコードが正しく動作することを確認するには、[Departments]\(部門\) タブを選択し、いずれかの部門の [Details]\(詳細\) を選択します。

部門の詳細

クエリを呼び出して他の型を返す

以前に、[概要] ページに学生統計表を作成し、各登録日の学生数を示しました。 Students エンティティ セット (_context.Students) からデータを取得し、LINQ を使用して結果をビュー モデル オブジェクトの一覧 EnrollmentDateGroup 投影しました。 LINQ を使用するのではなく、SQL 自体を記述するとします。 そのためには、エンティティ オブジェクト以外のものを返す SQL クエリを実行する必要があります。 EF Core 1.0 では、EF からデータベース接続を取得するために、ADO.NET コードを書くことがその方法の1つです。

HomeController.csで、About メソッドを次のコードに置き換えます。

public async Task<ActionResult> About()
{
    List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
    var conn = _context.Database.GetDbConnection();
    try
    {
        await conn.OpenAsync();
        using (var command = conn.CreateCommand())
        {
            string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
                + "FROM Person "
                + "WHERE Discriminator = 'Student' "
                + "GROUP BY EnrollmentDate";
            command.CommandText = query;
            DbDataReader reader = await command.ExecuteReaderAsync();

            if (reader.HasRows)
            {
                while (await reader.ReadAsync())
                {
                    var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
                    groups.Add(row);
                }
            }
            reader.Dispose();
        }
    }
    finally
    {
        conn.Close();
    }
    return View(groups);
}

using ステートメントを追加します。

using System.Data.Common;

アプリを実行し、[情報] ページに移動します。 以前と同じデータが表示されます。

概要ページ

更新クエリを呼び出す

Contoso University の管理者が、すべてのコースのクレジット数の変更など、データベースでグローバルな変更を実行するとします。 大学に多数のコースがある場合は、それらをすべてエンティティとして取得し、個別に変更するのは非効率的です。 このセクションでは、ユーザーがすべてのコースのクレジット数を変更する要因を指定できる Web ページを実装し、SQL UPDATE ステートメントを実行して変更を行います。 Web ページは次の図のようになります。

コースクレジットの更新ページ

CoursesController.csで、HttpGet と HttpPost の UpdateCourseCredits メソッドを追加します。

public IActionResult UpdateCourseCredits()
{
    return View();
}
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
    if (multiplier != null)
    {
        ViewData["RowsAffected"] = 
            await _context.Database.ExecuteSqlCommandAsync(
                "UPDATE Course SET Credits = Credits * {0}",
                parameters: multiplier);
    }
    return View();
}

コントローラーが HttpGet 要求を処理すると、ViewData["RowsAffected"]では何も返されません。前の図に示すように、ビューには空のテキスト ボックスと送信ボタンが表示されます。

[更新] ボタンをクリックすると、HttpPost メソッドが呼び出され、乗数の値がテキスト ボックスに入力されます。 次に、コースを更新し、影響を受ける行の数を ViewDataのビューに返す SQL を実行します。 ビューが RowsAffected 値を取得すると、更新された行数が表示されます。

ソリューション エクスプローラーで、Views/Courses フォルダーを右クリックし、[追加] > 新しい項目をクリックします。

[新しい項目の追加] ダイアログで、左側のウィンドウで [インストール済み] の下の [ASP.NET Core] をクリックし、[Razor 表示] をクリックして、新しいビューに UpdateCourseCredits.cshtmlという名前を付けます。

Views/Courses/UpdateCourseCredits.cshtmlで、テンプレート コードを次のコードに置き換えます。

@{
    ViewBag.Title = "UpdateCourseCredits";
}

<h2>Update Course Credits</h2>

@if (ViewData["RowsAffected"] == null)
{
    <form asp-action="UpdateCourseCredits">
        <div class="form-actions no-color">
            <p>
                Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
            </p>
            <p>
                <input type="submit" value="Update" class="btn btn-default" />
            </p>
        </div>
    </form>
}
@if (ViewData["RowsAffected"] != null)
{
    <p>
        Number of rows updated: @ViewData["RowsAffected"]
    </p>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

UpdateCourseCredits メソッドを実行するには、Courses タブを選択し、ブラウザーのアドレス バーの URL の末尾に "/UpdateCourseCredits" を追加します (例: http://localhost:5813/Courses/UpdateCourseCredits)。 テキスト ボックスに数値を入力します。

コースクレジットの更新ページ

[更新] をクリックします。 影響を受ける行の数が表示されます。

Course Credits ページの更新で影響を受けた行

[リストに戻る] をクリックすると、修正された単位数のコースの一覧が表示されます。

運用コードでは、更新によって常に有効なデータが生成されることに注意してください。 ここで示す簡略化されたコードは、5 を超える数値になるのに十分なクレジット数を乗算する可能性があります。 (Credits プロパティには [Range(0, 5)] 属性があります)。更新クエリは機能しますが、無効なデータにより、システムの他の部分でクレジット数が 5 以下であると想定される予期しない結果が発生する可能性があります。

生 SQL クエリの詳細については、「生 SQL クエリの 」を参照してください。

SQL クエリを調べる

データベースに送信された実際の SQL クエリを確認できると便利な場合があります。 ASP.NET Core の組み込みログ機能は、クエリと更新用の SQL を含むログを書き込むため、EF Core によって自動的に使用されます。 このセクションでは、SQL ログの例をいくつか示します。

StudentsController.cs を開き、Details メソッドで if (student == null) ステートメントにブレークポイントを設定します。

デバッグ モードでアプリを実行し、学生の [詳細] ページに移動します。

デバッグ出力を示す 出力 ウィンドウに移動すると、クエリが表示されます。

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
    SELECT TOP(1) [s0].[ID]
    FROM [Person] AS [s0]
    WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
    ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]

ここで驚くことがあるかもしれませんが、SQLが人物テーブルから最大2行 (TOP(2)) を選択します。 SingleOrDefaultAsync メソッドは、サーバー上で1行に解決されません。 その理由は次のとおりです。

  • クエリが複数の行を返す場合、メソッドは null を返します。
  • クエリが複数の行を返すかどうかを判断するには、EF が少なくとも 2 つを返すかどうかを確認する必要があります。

出力 ウィンドウでログ出力を取得するためには、デバッグ モードを使用したりブレークポイントで停止したりする必要はありません。 これは、出力を見たい時点でログ記録を停止する便利な方法です。 そうしないと、ログ記録が続行され、関心のある部分を見つけるために戻ってスクロールする必要があります。

抽象化レイヤーを作成する

多くの開発者は、Entity Framework で動作するコードのラッパーとして、リポジトリと作業単位パターンを実装するコードを記述します。 これらのパターンは、データ アクセス層とアプリケーションのビジネス ロジック 層の間に抽象化レイヤーを作成することを目的としています。 これらのパターンを実装すると、データ ストア内の変更からアプリケーションを分離し、自動単体テストまたはテスト駆動開発 (TDD) を容易にすることができます。 ただし、これらのパターンを実装するための追加のコードを記述することは、EF を使用するアプリケーションに最適な選択であるとは限りません。いくつかの理由があります。

  • EF コンテキスト クラス自体は、データ ストア固有のコードからコードを分離します。

  • EF コンテキスト クラスは、EF を使用して実行するデータベース更新の作業単位クラスとして機能できます。

  • EF には、リポジトリ コードを記述せずに TDD を実装するための機能が含まれています。

リポジトリと作業単位パターンを実装する方法については、このチュートリアル シリーズの Entity Framework 5 バージョン を参照してください。

Entity Framework Core は、テストに使用できるメモリ内データベース プロバイダーを実装します。 詳細については、「InMemory を使用したテスト」を参照してください。

変更の自動検出

Entity Framework では、エンティティの現在の値と元の値を比較することで、エンティティがどのように変更されたか (したがって、どの更新プログラムをデータベースに送信する必要がありますか) が決定されます。 元の値は、エンティティのクエリまたはアタッチ時に格納されます。 自動変更検出の原因となる方法の一部を次に示します。

  • DbContext.SaveChanges

  • DbContext.Entry

  • ChangeTracker.Entries

多数のエンティティを追跡していて、ループでこれらのメソッドの 1 つを何度も呼び出す場合は、ChangeTracker.AutoDetectChangesEnabled プロパティを使用して自動変更検出を一時的にオフにすることで、パフォーマンスが大幅に向上する可能性があります。 例えば:

_context.ChangeTracker.AutoDetectChangesEnabled = false;

EF Core ソースコードと開発計画

Entity Framework Core ソースは https://github.com/dotnet/efcoreにあります。 EF Core リポジトリには、夜間ビルド、問題追跡、機能仕様、設計会議ノート、および将来の開発 のロードマップが含まれています。 バグを報告したり見つけたり、投稿したりすることができます。

ソース コードは開かれていますが、Entity Framework Core は Microsoft 製品として完全にサポートされています。 Microsoft Entity Framework チームは、受け入れられるコントリビューションを制御し続け、すべてのコード変更をテストして各リリースの品質を確認します。

既存のデータベースからのリバース エンジニアリング

既存のデータベースのエンティティ クラスを含むデータ モデルをリバース エンジニアリングするには、スキャフォールディング dbcontext コマンドを使用します。 入門チュートリアルのを参照してください。

動的 LINQ を使用してコードを簡略化する

このシリーズ 3 番目のチュートリアルでは、switch ステートメントで列名をハードコーディングして LINQ コードを記述する方法を示します。 2 つの列から選択すると、これは正常に動作しますが、多くの列がある場合は、コードが詳細になる可能性があります。 この問題を解決するには、EF.Property メソッドを使用して、プロパティの名前を文字列として指定します。 この方法を試すには、StudentsControllerIndex メソッドを次のコードに置き換えます。

 public async Task<IActionResult> Index(
     string sortOrder,
     string currentFilter,
     string searchString,
     int? pageNumber)
 {
     ViewData["CurrentSort"] = sortOrder;
     ViewData["NameSortParm"] = 
         String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
     ViewData["DateSortParm"] = 
         sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";

     if (searchString != null)
     {
         pageNumber = 1;
     }
     else
     {
         searchString = currentFilter;
     }

     ViewData["CurrentFilter"] = searchString;

     var students = from s in _context.Students
                    select s;
     
     if (!String.IsNullOrEmpty(searchString))
     {
         students = students.Where(s => s.LastName.Contains(searchString)
                                || s.FirstMidName.Contains(searchString));
     }

     if (string.IsNullOrEmpty(sortOrder))
     {
         sortOrder = "LastName";
     }

     bool descending = false;
     if (sortOrder.EndsWith("_desc"))
     {
         sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
         descending = true;
     }

     if (descending)
     {
         students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
     }
     else
     {
         students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
     }

     int pageSize = 3;
     return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), 
         pageNumber ?? 1, pageSize));
 }

謝辞

Tom Dykstra と Rick Anderson (twitter @RickAndMSFT) はこのチュートリアルを書きました。 Rowan Miller、Diego Vega、および Entity Framework チームの他のメンバーは、コード レビューを支援し、チュートリアルのコードの記述中に発生した問題のデバッグを支援しました。 John Parente と Paul Goldman は、ASP.NET Core 2.2 のチュートリアルの更新に取り組んだ。

一般的なエラーのトラブルシューティング

別のプロセスで使用される ContosoUniversity.dll

エラーメッセージ:

'... を開くことができません。bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll' は別のプロセスで使用されているためです。

解決:

IIS Express でサイトを停止します。 Windows システム トレイに戻り、IIS Express を見つけてそのアイコンを右クリックし、Contoso University サイトを選択し、[サイトの停止] をクリックします。

Up メソッドと Down メソッドでコードを使用せずに移行がスキャフォールディングされる

考えられる原因:

EF CLI コマンドは、コード ファイルを自動的に閉じて保存することはありません。 migrations add コマンドの実行時に変更を保存していない場合、EF は変更を見つけることができません。

解決:

migrations remove コマンドを実行し、コードの変更を保存して、migrations add コマンドを再実行します。

データベース更新の実行中のエラー

既存のデータを含むデータベースでスキーマを変更すると、他のエラーが発生する可能性があります。 解決できない移行エラーが発生した場合は、接続文字列のデータベース名を変更するか、データベースを削除できます。 新しいデータベースでは、移行するデータがなく、update-database コマンドがエラーなしで完了する可能性がはるかに高くなります。

最も簡単な方法は、appsettings.jsonでデータベースの名前を変更することです。 次回 database update実行すると、新しいデータベースが作成されます。

SSOX でデータベースを削除するには、データベースを右クリックし、[ 削除] をクリックし、[ データベース の削除] ダイアログ ボックスで [既存の接続 を閉じる] を選択し、[ OK] をクリックします。

CLI を使用してデータベースを削除するには、database drop CLI コマンドを実行します。

dotnet ef database drop

SQL Server インスタンスの検索中にエラーが発生しました

エラーメッセージ:

SQL Server への接続の確立中に、ネットワーク関連またはインスタンス固有のエラーが発生しました。 サーバーが見つからなかったか、アクセスできませんでした。 インスタンス名が正しいこと、およびリモート接続を許可するように SQL Server が構成されていることを確認します。 (プロバイダー: SQL ネットワーク インターフェイス、エラー: 26 - 指定されたサーバー/インスタンスの検索エラー)

解決:

接続文字列を確認します。 データベース ファイルを手動で削除した場合は、新しいデータベースからやり直すように、構築文字列内のデータベースの名前を変更します。

コードを取得する

完成したアプリケーションをダウンロードまたは表示します。

その他のリソース

EF Coreの詳細については、Entity Framework Core のドキュメントを参照してください。 書籍「Entity Framework Core in Action」も利用できます。

Web アプリをデプロイする方法については、「CoreASP.NET ホストしてデプロイする」を参照してください。

認証や承認など、ASP.NET Core MVC に関連するその他のトピックについては、「ASP.NET Coreの概要」を参照してください。

信頼性が高く、セキュリティで保護され、パフォーマンスが高く、テスト可能でスケーラブルな ASP.NET Core アプリの作成に関するガイダンスについては、Enterprise Web アプリ パターンを参照してください。 パターンを実装する完全な運用品質のサンプル Web アプリを使用できます。

次の手順

このチュートリアルでは、次の操作を行います。

  • 実行された生の SQL クエリ
  • エンティティを返すクエリを呼び出しました
  • 他の型を返すクエリを呼び出しました
  • 更新クエリの呼び出し
  • 調査された SQL クエリ
  • 抽象化レイヤーを作成しました
  • 変更の自動検出について学習しました
  • ソース コードと開発計画 EF Core について学習しました
  • 動的 LINQ を使用してコードを簡略化する方法について説明しました

これにより、ASP.NET Core MVC アプリケーションでの Entity Framework Core の使用に関するこの一連のチュートリアルが完了します。 このシリーズは新しいデータベースで動作しました。別の方法は、既存のデータベース からモデルをリバース エンジニアリングすることです。