ASP.NET Core での Entity Framework Core を使用した Razor Pages - チュートリアル 1/8

作成者: Tom DykstraJeremy LiknessJon P Smith

これは、ASP.NET Core Razor Pages アプリでの Entity Framework (EF) Core の使用方法を示す一連のチュートリアルの 1 番目です。 このチュートリアルでは、架空の Contoso University の Web サイトを構築します。 サイトには、学生の受け付け、講座の作成、講師の割り当てなどの機能が含まれます。 このチュートリアルでは、コード ファーストのアプローチを使用します。 データベース ファーストのアプローチを使用してこのチュートリアルを実行する方法の詳細については、こちらの Github イシューをご覧ください。

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

前提条件

  • Razor Pages を初めて使用する場合は、このチュートリアルを開始する前に、Razor Pages の概要チュートリアル シリーズをご覧ください。

データベース エンジン

Visual Studio の手順では、SQL Server LocalDB を使用します。これは、Windows 上でのみ実行される SQL Server Express のバージョンです。

トラブルシューティング

解決できない問題が発生した場合は、コードを完成したプロジェクトと比較します。 ヘルプが必要なときは、ASP.NET Core タグまたは EF Core タグを使用して、StackOverflow.com に質問を投稿することをお勧めします。

サンプル アプリ

このチュートリアル シリーズで作成するアプリは、大学向けの基本的な Web サイトです。 ユーザーは学生、講座、講師の情報を見たり、更新したりできます。 チュートリアルで作成する画面は次のようになります。

Students Index page

Students Edit page

このサイトの UI スタイルは、組み込みのプロジェクト テンプレートに基づいています。 このチュートリアルでは、UI をカスタマイズする方法ではなく、EF Core と ASP.NET Core の使用方法について主に説明します。

省略可能: サンプルのダウンロードをビルドする

この手順は省略可能です。 解決できない問題がある場合は、完成したアプリをビルドすることをお勧めします。 解決できない問題が発生した場合は、コードを完成したプロジェクトと比較します。 ダウンロードの方法はこちらをご覧ください。

ContosoUniversity.csproj を選択してプロジェクトを開きます。

  • プロジェクトをビルドします。

  • パッケージ マネージャー コンソール (PMC) で、次のコマンドを実行します。

    Update-Database
    

プロジェクトを実行して、データベースをシードします。

Web アプリプロジェクトを作成する

  1. Visual Studio 2022 を開始し、[新しいプロジェクトの作成] を選択します。

    Create a new project from the start window

  2. [新しいプロジェクトの作成] ダイアログで、[ASP.NET Core Web アプリ][次へ] の順に選択します。

    Create an ASP.NET Core Web App

  3. [新しいプロジェクトの構成] ダイアログで、[プロジェクト名] に「ContosoUniversity」と入力します。 サンプル コードのコピーおよび貼り付けを行う際に名前空間が一致するように、プロジェクトに ContosoUniversity という (大文字と小文字が一致する) 名前を付けることが重要です。

  4. [次へ] を選択します。

  5. [追加情報] ダイアログで、[.NET 6.0 (長期的なサポート)] を選択し、[作成] を選択します。

    Additional information

サイトのスタイルを設定する

次のコードをコピーし、Pages/Shared/_Layout.cshtml ファイルに貼り付けます。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/ContosoUniversity.styles.css" asp-append-version="true" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">                        
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

このレイアウト ファイルによってサイト ヘッダー、フッター、およびメニューが設定されます。 上記のコードは、次の変更を加えます。

  • "ContosoUniversity" をいずれも "Contoso University" へ変更。 これは 3 回出てきます。
  • Home および Privacy メニュー エントリが削除されます。
  • [バージョン情報][学生][コース][講師] 、および [部門] のエントリが追加されます。

Pages/Index.cshtml で、ファイルの内容を次のコードに置き換えます。

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
@*                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
*@                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
@*                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
*@                </p>
            </div>
        </div>
    </div>
</div>

上記のコードでは、ASP.NET Core に関するテキストがこのアプリに関するテキストに置き換えられます。

アプリを実行して、ホームページが表示されることを確認します。

データ モデル

以降のセクションでは、データ モデルを作成します。

Course-Enrollment-Student data model diagram

学生は任意の数のコースに登録でき、コースには任意の数の学生を登録できます。

Student エンティティ

Student entity diagram

  • プロジェクト フォルダー内に Models フォルダーを作成します。
  • 次のコードを使用して Models/Student.cs を作成します。
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

ID プロパティは、このクラスに相当するデータベース テーブルの主キー列になります。 既定では、EF Core は、ID または classnameID という名前のプロパティを主キーとして解釈します。 そのため、Student クラスの主キーの自動的に認識される代替名は、StudentID です。 詳細については、EF Core のキーに関するページを参照してください。

Enrollments プロパティはナビゲーション プロパティです。 ナビゲーション プロパティには、このエンティティに関連する他のエンティティが含まれます。 この例では、Student エンティティの Enrollments プロパティによって、その Student に関連するすべての Enrollment エンティティが保持されます。 たとえば、データベース内のある Student 行に 2 つの関連する Enrollment 行がある場合、Enrollments ナビゲーション プロパティにその 2 つの Enrollment エンティティが含まれます。

データベースでは、StudentID 列に学生の ID 値が含まれている場合、Enrollment 行が Student 行に関連付けられます。 たとえば、Student 行の ID が 1 であるとします。 関連する Enrollment 行の StudentID は 1 になります。 StudentID は、Enrollment テーブルの ''外部キー'' です。

Enrollments プロパティは、複数の関連する Enrollment エンティティが存在する可能性があるため、ICollection<Enrollment> として定義されます。 他のコレクション型 (List<Enrollment>HashSet<Enrollment> など) を使用できます。 ICollection<Enrollment> を使用する場合、EF Core によって HashSet<Enrollment> コレクションが既定で作成されます。

Enrollment エンティティ

Enrollment entity diagram

次のコードを使用して Models/Enrollment.cs を作成します。

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

EnrollmentID プロパティは主キーです。このエンティティは、ID ではなく classnameID パターンを使用します。 実稼働データ モデルでは、多くの開発者が 1 つのパターンを選択し、一貫して使用します。 このチュートリアルでは、どちらも機能することを示すためだけに両方を使用します。 classname を指定せずに ID を使用すると、一部の種類のデータ モデルの変更の実装が容易になります。

Grade プロパティは enum です。 Grade の型宣言の後の疑問符は、Grade プロパティが nullable であることを示します。 null という成績は、0 の成績とは異なります。null は成績がわからないか、まだ評価されていないことを意味します。

StudentID プロパティは外部キーです。それに対応するナビゲーション プロパティは Student です。 Enrollment エンティティは 1 つの Studentエンティティに関連付けられますので、このプロパティには Student エンティティが 1 つ含まれます。

CourseID プロパティは外部キーです。それに対応するナビゲーション プロパティは Course です。 Enrollment エンティティは 1 つの Course エンティティに関連付けられます。

EF Core は、<navigation property name><primary key property name> という名前のプロパティを外部キーとして解釈します。 たとえば、Student ナビゲーション プロパティの StudentID は外部キーです。Student エンティティの主キーが ID であるからです。 外部キーのプロパティには <primary key property name> という名前を付けることもできます。 たとえば、CourseID にします。Course エンティティの主キーが CourseID であるからです。

Course エンティティ

Course entity diagram

次のコードを使用して Models/Course.cs を作成します。

using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Enrollments プロパティはナビゲーション プロパティです。 1 つの Course エンティティにたくさんの Enrollment エンティティを関連付けることができます。

DatabaseGenerated 属性によって、主キーをデータベースに生成させるのではなく、アプリで指定することできます。

アプリをビルドします。 コンパイラにより、null 値の処理方法に関するいくつかの警告が生成されます。 詳細については、この GitHub イシューnull 許容参照型、および「チュートリアル: null 許容参照型と null 非許容参照型を使用して設計意図をもっと明確に示す」を参照してください。

null 許容参照型から警告を除去するには、ContosoUniversity.csproj ファイルから次の行を削除します。

<Nullable>enable</Nullable>

スキャフォールディング エンジンでは現在、null 許容参照型はサポートされていないため、スキャフォールディングで使用されるモデルでもサポートされません。

プロジェクトがコンパイラ警告なしでビルドされるように、Pages/Error.cshtml.cspublic string? RequestId { get; set; } から ? null 許容参照型の注釈を削除します。

Student ページのスキャフォールディング

このセクションでは、次のものを生成するために ASP.NET Core スキャフォールディング ツールが使用されます。

  • EF CoreDbContext クラス。 コンテキストは、定められたデータ モデルに対し、Entity Framework 機能を調整するメイン クラスです。 これは Microsoft.EntityFrameworkCore.DbContext クラスから派生します。
  • Student エンティティの作成、読み取り、更新、および削除 (CRUD) 操作を処理する Razor ページ。
  • Pages/Students フォルダーを作成します。
  • ソリューション エクスプローラーで、[Pages/Students] フォルダーを右クリックし、[追加]>[スキャフォールディングされた新しい項目] の順に選択します。
  • [新しいスキャフォールディング アイテムの追加] ダイアログで次のようにします。
    • 左側のタブで、[インストール済み] > [共通] > [Razor Pages] の順に選択します
    • [Entity Framework を使用する Razor Pages (CRUD)]>[追加] の順に選択します。
  • [Add Razor Pages using Entity Framework (CRUD)]\(Entity Framework を使用して Razor Pages (CRUD) を追加する\) ダイアログで、次のことを行います。
    • [モデル クラス] ドロップダウンで、 [Student (ContosoUniversity.Models)] を選択します。
    • [データ コンテキスト クラス] 行で、[+] (プラス) 記号を選択します。
      • データ コンテキスト名が、ContosoUniversityContext ではなく SchoolContext で終わるように変更します。 更新されたコンテキスト名: ContosoUniversity.Data.SchoolContext
      • [追加] 選択してデータ コンテキスト クラスの追加を完了します。
      • [追加] を選択して、 [Razor Pages の追加] ダイアログを終了します。

次のパッケージが自動的にインストールされます。

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

前の手順が失敗した場合は、プロジェクトをビルドし、スキャフォールディング手順を再試行します。

スキャフォールディング プロセスは次のとおりです。

  • Pages/Students フォルダーに Razor ページを作成します。
    • Create.cshtml および Create.cshtml.cs
    • Delete.cshtml および Delete.cshtml.cs
    • Details.cshtml および Details.cshtml.cs
    • Edit.cshtml および Edit.cshtml.cs
    • Index.cshtml および Index.cshtml.cs
  • Data/SchoolContext.cs が作成されます。
  • Program.cs の依存関係の挿入にコンテキストを追加します。
  • データベース接続文字列を appsettings.json に追加します。

データベース接続文字列

スキャフォールディング ツールを使用すると、 appsettings.json ファイルに接続文字列が生成されます。

この接続文字列によって SQL Server LocalDB が指定されます。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext-0e9;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB は SQL Server Express データベース エンジンの軽量版であり、実稼働ではなく、アプリの開発を意図して設計されています。 既定では、LocalDB は C:/Users/<user> ディレクトリに .mdf ファイルを作成します。

データベース コンテキスト クラスを更新する

定められたデータ モデルの EF Core 機能を調整するメイン クラスは、データベース コンテキスト クラスです。 コンテキストは Microsoft.EntityFrameworkCore.DbContext から派生します。 コンテキストによって、データ モデルに含めるエンティティが指定されます。 このプロジェクトでは、クラスに SchoolContext という名前が付けられています。

次のコードを使用して Data/SchoolContext.cs を更新します。

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

上記のコードは、単数形の DbSet<Student> Student から複数形の DbSet<Student> Students に変更されます。 Razor Pages のコードが新しい DBSet 名と一致するようにするには、次からグローバル変更を行います: _context.Student. から _context.Students. にグローバルに変更します

8 回の出現があります。

エンティティ セットには複数のエンティティが含まれているため、開発者の多くは DBSet プロパティ名を複数形にすることを好みます。

強調表示されたコード:

  • エンティティ セットごとに DbSet<TEntity> プロパティを作成します。 EF Core の用語では:
    • エンティティ セットは通常、データベース テーブルに対応します。
    • エンティティはテーブル内の行に対応します。
  • OnModelCreating. OnModelCreating:
    • SchoolContext が初期化されたとき、ただしモデルがロックダウンされてコンテキストの初期化に使用される前に呼び出されます。
    • チュートリアルの後半で Student エンティティに他のエンティティへの参照が含まれるようになるため、必須となります。

将来のリリースでこの問題を修正する予定です。

Program.cs

ASP.NET Core には、依存関係挿入が組み込まれています。 サービス (SchoolContext など) は、アプリの起動時に依存関係の挿入に登録されます。 これらのサービスを必要とするコンポーネント (Razor Pages など) には、コンストラクターのパラメーターを介してこれらのサービスが指定されます。 データベース コンテキスト インスタンスを取得するコンストラクター コードは、この後のチュートリアルで示します。

スキャフォールディング ツールにより、コンテキスト クラスが依存関係挿入コンテナーに自動的に登録されました。

次の強調表示された行がスキャフォールダーによって追加されました。

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

DbContextOptions オブジェクトでメソッドが呼び出され、接続文字列の名前がコンテキストに渡されます。 ローカル開発の場合、ASP.NET Core 構成システムによって appsettings.json または appsettings.Development.json ファイルから接続文字列が読み取られます。

データベース例外フィルターを追加する

次のコードに示されているように、UseMigrationsEndPointAddDatabaseDeveloperPageExceptionFilter を追加します。

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore NuGet パッケージを追加します。

パッケージ マネージャー コンソールで、次のように入力して NuGet パッケージを追加します。

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore NuGet パッケージには Entity Framework Core のエラー ページ用の ASP.NET Core ミドルウェアが用意されています。 このミドルウェアは、Entity Framework Core の移行に関するエラーを検出して診断するのに役立ちます。

により、EF 移行エラーについて、AddDatabaseDeveloperPageExceptionFilter開発環境で役立つエラー情報が提供されます。

データベースの作成

データベースが存在しない場合は、Program.cs を更新して作成します。

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    // DbInitializer.Initialize(context);
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

コンテキストのデータベースが存在する場合、EnsureCreated メソッドは何も実行しません。 データベースが存在しない場合は、データベースとスキーマが作成されます。 EnsureCreated により、データ モデルの変更を処理するための次のワークフローが有効になります。

  • データベースを削除します。 既存のデータはすべて失われます。
  • データ モデルを変更します。 たとえば、EmailAddress フィールドを追加します。
  • アプリを実行します。
  • EnsureCreated により、新しいスキーマを使用してデータベースが作成されます。

このワークフローは、データを保持する必要がない限り、スキーマが急速に進化する開発の早い段階で機能します。 データベースに入力されたデータを保持する必要がある場合は、状況は異なります。 その場合は、移行を使用します。

このチュートリアル シリーズの後半では、EnsureCreated によって作成されたデータベースが削除され、移行が使用されます。 EnsureCreated によって作成されたデータベースは、移行を使用して更新することはできません。

アプリのテスト

  • アプリを実行します。
  • [Students] リンクを選択し、 [新規作成] を選択します。
  • [編集]、[詳細]、および [削除] の各リンクをテストします。

データベースのシード

EnsureCreated メソッドは、空のデータベースを作成します。 このセクションでは、データベースにテスト データを入力するコードを追加します。

次のコードを使用して Data/DbInitializer.cs を作成します。

using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

このコードは、データベースに学生が存在するかどうかを確認します。 学生が存在しない場合は、テスト データがデータベースに追加されます。 パフォーマンスを最適化するために、List<T> コレクションではなく配列にテスト データを作成します。

  • Program.cs で、DbInitializer.Initialize 行から // を削除します。
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
}
  • アプリが実行されている場合は停止し、パッケージ マネージャー コンソール (PMC) で次のコマンドを実行します。

    Drop-Database -Confirm
    
    
  • Y で応答すると、データベースが削除されます。

  • アプリを再起動します。
  • Students ページを選択すると、シードされたデータが表示されます。

データベースを表示する

  • Visual Studio の [表示] メニューから SQL Server オブジェクト エクスプローラー (SSOX) を開きます。
  • SSOX で、(localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID} の順に選択します。 前に指定したコンテキスト名にダッシュと GUID が追加されて、データベース名が生成されます。
  • [Tables](テーブル) ノードを展開します。
  • [Student](学生) テーブルを右クリックし、 [View Data](データの表示) をクリックすると、作成された列とテーブルに挿入された行が表示されます。
  • Student テーブルを右クリックして [コードの表示] をクリックし、Student モデルが Student テーブル スキーマにどのようにマップされるかを確認します。

ASP.NET Core Web アプリでの非同期 EF メソッド

ASP.NET Core と EF Core では、非同期プログラミングが既定のモードです。

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

非同期コードが実行時に発生させるオーバーヘッドは少量です。 トラフィックが少ない場合、パフォーマンスに与える影響は無視して構わない程度です。トラフィックが多い場合、相当なパフォーマンス改善が見込まれます。

次のコードでは、キーワード async、戻り値 Task、キーワード await、メソッド ToListAsync によりコードの実行が非同期になります。

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • キーワード async は次のことをコンパイラに指示します。
    • メソッド本文の一部にコールバックを生成する。
    • 返される Task オブジェクトを作成する。
  • 戻り値の型 Task は進行中の作業を表します。
  • キーワード await により、コンパイラはメソッドを 2 つに分割します。 最初の部分は、非同期で開始される操作で終わります。 2 つ目の部分は、操作の完了時に呼び出されるコールバック メソッドに入ります。
  • ToListAsync は、ToList 拡張メソッドの非同期バージョンです。

EF Core を使用する非同期コードを記述するときに注意すべき点:

  • クエリやコマンドをデータベースに送信するステートメントのみが非同期で実行されます。 これには、ToListAsyncSingleOrDefaultAsyncFirstOrDefaultAsyncSaveChangesAsync が含まれます。 var students = context.Students.Where(s => s.LastName == "Davolio") など、IQueryable を変更するだけのステートメントは含まれません。
  • EF Core コンテキストはスレッド セーフではありません。複数の操作を並列実行しないでください。
  • 非同期コードのパフォーマンス上の利点を最大限に活用するには、クエリをデータベースに送信させる EF Core メソッドを (ページングなどのための) ライブラリ パッケージで呼び出す場合、そのライブラリ パッケージで非同期が利用されていることを確認します。

.NET での非同期プログラミングの詳細については、「非同期の概要」と「Async および Await を使用した非同期プログラミング (C#)」を参照してください。

警告

Microsoft.Data.SqlClient の非同期実装には、いくつか既知の問題があります (#593#601、その他)。 予期しないパフォーマンスの問題が発生する場合、特に大きなテキストまたはバイナリ値を処理する場合は、代わりに同期コマンドの実行を使用してみてください。

パフォーマンスに関する考慮事項

一般に、Web ページで任意の数の行を読み込むべきではありません。 クエリでは、ページングまたは制限アプローチを使用する必要があります。 たとえば、上記のクエリでは Take を使用して、返される行を制限できます。

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

ビューで大きいテーブルを列挙すると、列挙の途中でデータベース例外が発生した場合、部分的に構築された HTTP 200 応答が返される可能性があります。

ページングについては、このチュートリアルで後述します。

詳細については、「パフォーマンスに関する考慮事項 (EF)」を参照してください。

次の手順

開発用に SQLite を、運用環境に SQL Server を使用する

これは、ASP.NET Core Razor Pages アプリでの Entity Framework (EF) Core の使用方法を示す一連のチュートリアルの 1 番目です。 このチュートリアルでは、架空の Contoso University の Web サイトを構築します。 サイトには、学生の受け付け、講座の作成、講師の割り当てなどの機能が含まれます。 このチュートリアルでは、コード ファーストのアプローチを使用します。 データベース ファーストのアプローチを使用してこのチュートリアルを実行する方法の詳細については、こちらの Github イシューをご覧ください。

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

前提条件

  • Razor Pages を初めて使用する場合は、このチュートリアルを開始する前に、Razor Pages の概要チュートリアル シリーズをご覧ください。

データベース エンジン

Visual Studio の手順では、SQL Server LocalDB を使用します。これは、Windows 上でのみ実行される SQL Server Express のバージョンです。

トラブルシューティング

解決できない問題が発生した場合は、コードを完成したプロジェクトと比較します。 ヘルプが必要なときは、ASP.NET Core タグまたは EF Core タグを使用して、StackOverflow.com に質問を投稿することをお勧めします。

サンプル アプリ

このチュートリアル シリーズで作成するアプリは、大学向けの基本的な Web サイトです。 ユーザーは学生、講座、講師の情報を見たり、更新したりできます。 チュートリアルで作成する画面は次のようになります。

Students Index page

Students Edit page

このサイトの UI スタイルは、組み込みのプロジェクト テンプレートに基づいています。 このチュートリアルでは、UI をカスタマイズする方法ではなく、EF Core と ASP.NET Core の使用方法について主に説明します。

省略可能: サンプルのダウンロードをビルドする

この手順は省略可能です。 解決できない問題がある場合は、完成したアプリをビルドすることをお勧めします。 解決できない問題が発生した場合は、コードを完成したプロジェクトと比較します。 ダウンロードの方法はこちらをご覧ください。

ContosoUniversity.csproj を選択してプロジェクトを開きます。

  • プロジェクトをビルドします。
  • パッケージ マネージャー コンソール (PMC) で、次のコマンドを実行します。
Update-Database

プロジェクトを実行して、データベースをシードします。

Web アプリプロジェクトを作成する

  1. Visual Studio を起動し、 [新しいプロジェクトの作成] を選択します。
  2. [新しいプロジェクトの作成] ダイアログで、[ASP.NET Core Web アプリケーション]>[次へ] の順に選択します。
  3. [新しいプロジェクトの構成] ダイアログで、 [プロジェクト名] に「ContosoUniversity」と入力します。 コードをコピーするときに各namespaceが一致するように、この正確な名前 (大文字と小文字を含む) を使用することが重要です。
  4. [作成] を選択します
  5. [新しい ASP.NET Core Web アプリケーションの作成] ダイアログで、次のものを選択します。
    1. ドロップダウンで [.NET Core][ASP.NET Core 5.0]
    2. ASP.NET Core Web アプリ
    3. 作成New ASP.NET Core Project dialog

サイトのスタイルを設定する

次のコードをコピーし、Pages/Shared/_Layout.cshtml ファイルに貼り付けます。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

このレイアウト ファイルによってサイト ヘッダー、フッター、およびメニューが設定されます。 上記のコードは、次の変更を加えます。

  • "ContosoUniversity" をいずれも "Contoso University" へ変更。 これは 3 回出てきます。
  • Home および Privacy メニュー エントリが削除されます。
  • [バージョン情報][学生][コース][講師] 、および [部門] のエントリが追加されます。

Pages/Index.cshtml で、ファイルの内容を次のコードに置き換えます。

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

上記のコードでは、ASP.NET Core に関するテキストがこのアプリに関するテキストに置き換えられます。

アプリを実行して、ホームページが表示されることを確認します。

データ モデル

以降のセクションでは、データ モデルを作成します。

Course-Enrollment-Student data model diagram

学生は任意の数のコースに登録でき、コースには任意の数の学生を登録できます。

Student エンティティ

Student entity diagram

  • プロジェクト フォルダー内に Models フォルダーを作成します。

  • 次のコードを使用して Models/Student.cs を作成します。

    using System;
    using System.Collections.Generic;
    
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

ID プロパティは、このクラスに相当するデータベース テーブルの主キー列になります。 既定では、EF Core は、ID または classnameID という名前のプロパティを主キーとして解釈します。 そのため、Student クラスの主キーの自動的に認識される代替名は、StudentID です。 詳細については、EF Core のキーに関するページを参照してください。

Enrollments プロパティはナビゲーション プロパティです。 ナビゲーション プロパティには、このエンティティに関連する他のエンティティが含まれます。 この例では、Student エンティティの Enrollments プロパティによって、その Student に関連するすべての Enrollment エンティティが保持されます。 たとえば、データベース内のある Student 行に 2 つの関連する Enrollment 行がある場合、Enrollments ナビゲーション プロパティにその 2 つの Enrollment エンティティが含まれます。

データベースでは、StudentID 列に学生の ID 値が含まれている場合、Enrollment 行が Student 行に関連付けられます。 たとえば、Student 行の ID が 1 であるとします。 関連する Enrollment 行の StudentID は 1 になります。 StudentID は、Enrollment テーブルの ''外部キー'' です。

Enrollments プロパティは、複数の関連する Enrollment エンティティが存在する可能性があるため、ICollection<Enrollment> として定義されます。 他のコレクション型 (List<Enrollment>HashSet<Enrollment> など) を使用できます。 ICollection<Enrollment> を使用する場合、EF Core によって HashSet<Enrollment> コレクションが既定で作成されます。

Enrollment エンティティ

Enrollment entity diagram

次のコードを使用して Models/Enrollment.cs を作成します。

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

EnrollmentID プロパティは主キーです。このエンティティは、ID ではなく classnameID パターンを使用します。 実稼働データ モデルでは、多くの開発者が 1 つのパターンを選択し、一貫して使用します。 このチュートリアルでは、どちらも機能することを示すためだけに両方を使用します。 classname を指定せずに ID を使用すると、一部の種類のデータ モデルの変更の実装が容易になります。

Grade プロパティは enum です。 Grade の型宣言の後の疑問符は、Grade プロパティが nullable であることを示します。 null という成績は、0 の成績とは異なります。null は成績がわからないか、まだ評価されていないことを意味します。

StudentID プロパティは外部キーです。それに対応するナビゲーション プロパティは Student です。 Enrollment エンティティは 1 つの Studentエンティティに関連付けられますので、このプロパティには Student エンティティが 1 つ含まれます。

CourseID プロパティは外部キーです。それに対応するナビゲーション プロパティは Course です。 Enrollment エンティティは 1 つの Course エンティティに関連付けられます。

EF Core は、<navigation property name><primary key property name> という名前のプロパティを外部キーとして解釈します。 たとえば、Student ナビゲーション プロパティの StudentID は外部キーです。Student エンティティの主キーが ID であるからです。 外部キーのプロパティには <primary key property name> という名前を付けることもできます。 たとえば、CourseID にします。Course エンティティの主キーが CourseID であるからです。

Course エンティティ

Course entity diagram

次のコードを使用して Models/Course.cs を作成します。

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Enrollments プロパティはナビゲーション プロパティです。 1 つの Course エンティティにたくさんの Enrollment エンティティを関連付けることができます。

DatabaseGenerated 属性によって、主キーをデータベースに生成させるのではなく、アプリで指定することできます。

プロジェクトをビルドし、コンパイラ エラーがないことを検証します。

Student ページのスキャフォールディング

このセクションでは、次のものを生成するために ASP.NET Core スキャフォールディング ツールが使用されます。

  • EF CoreDbContext クラス。 コンテキストは、定められたデータ モデルに対し、Entity Framework 機能を調整するメイン クラスです。 これは Microsoft.EntityFrameworkCore.DbContext クラスから派生します。
  • Student エンティティの作成、読み取り、更新、および削除 (CRUD) 操作を処理する Razor ページ。
  • Pages/Students フォルダーを作成します。
  • ソリューション エクスプローラーで、[Pages/Students] フォルダーを右クリックし、[追加]>[スキャフォールディングされた新しい項目] の順に選択します。
  • [新しいスキャフォールディング アイテムの追加] ダイアログで次のようにします。
    • 左側のタブで、[インストール済み] > [共通] > [Razor Pages] の順に選択します
    • [Entity Framework を使用する Razor Pages (CRUD)]>[追加] の順に選択します。
  • [Add Razor Pages using Entity Framework (CRUD)]\(Entity Framework を使用して Razor Pages (CRUD) を追加する\) ダイアログで、次のことを行います。
    • [モデル クラス] ドロップダウンで、 [Student (ContosoUniversity.Models)] を選択します。
    • [データ コンテキスト クラス] 行で、[+] (プラス) 記号を選択します。
      • データ コンテキスト名が、ContosoUniversityContext ではなく SchoolContext で終わるように変更します。 更新されたコンテキスト名: ContosoUniversity.Data.SchoolContext
      • [追加] 選択してデータ コンテキスト クラスの追加を完了します。
      • [追加] を選択して、 [Razor Pages の追加] ダイアログを終了します。

スキャフォールディングがエラー 'Install the package Microsoft.VisualStudio.Web.CodeGeneration.Design and try again.' で失敗した場合は、スキャフォールディング ツールを再度実行するか、この GitHub イシューを参照してください。

次のパッケージが自動的にインストールされます。

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

前の手順が失敗した場合は、プロジェクトをビルドし、スキャフォールディング手順を再試行します。

スキャフォールディング プロセスは次のとおりです。

  • Pages/Students フォルダーに Razor ページを作成します。
    • Create.cshtml および Create.cshtml.cs
    • Delete.cshtml および Delete.cshtml.cs
    • Details.cshtml および Details.cshtml.cs
    • Edit.cshtml および Edit.cshtml.cs
    • Index.cshtml および Index.cshtml.cs
  • Data/SchoolContext.cs が作成されます。
  • Startup.cs の依存関係の挿入にコンテキストを追加します。
  • データベース接続文字列を appsettings.json に追加します。

データベース接続文字列

スキャフォールディング ツールを使用すると、 appsettings.json ファイルに接続文字列が生成されます。

この接続文字列によって SQL Server LocalDB が指定されます。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB は SQL Server Express データベース エンジンの軽量版であり、実稼働ではなく、アプリの開発を意図して設計されています。 既定では、LocalDB は C:/Users/<user> ディレクトリに .mdf ファイルを作成します。

データベース コンテキスト クラスを更新する

定められたデータ モデルの EF Core 機能を調整するメイン クラスは、データベース コンテキスト クラスです。 コンテキストは Microsoft.EntityFrameworkCore.DbContext から派生します。 コンテキストによって、データ モデルに含めるエンティティが指定されます。 このプロジェクトでは、クラスに SchoolContext という名前が付けられています。

次のコードを使用して Data/SchoolContext.cs を更新します。

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

上記のコードは、単数形の DbSet<Student> Student から複数形の DbSet<Student> Students に変更されます。 Razor Pages のコードが新しい DBSet 名と一致するようにするには、次からグローバル変更を行います: _context.Student. から _context.Students. にグローバルに変更します

8 回の出現があります。

エンティティ セットには複数のエンティティが含まれているため、開発者の多くは DBSet プロパティ名を複数形にすることを好みます。

強調表示されたコード:

  • エンティティ セットごとに DbSet<TEntity> プロパティを作成します。 EF Core の用語では:
    • エンティティ セットは通常、データベース テーブルに対応します。
    • エンティティはテーブル内の行に対応します。
  • OnModelCreating. OnModelCreating:
    • SchoolContext が初期化されたとき、ただしモデルがロックダウンされてコンテキストの初期化に使用される前に呼び出されます。
    • チュートリアルの後半で Student エンティティに他のエンティティへの参照が含まれるようになるため、必須となります。

プロジェクトをビルドし、コンパイラ エラーがないことを確認します。

Startup.cs

ASP.NET Core には、依存関係挿入が組み込まれています。 サービス (SchoolContext など) は、アプリの起動時に依存関係の挿入に登録されます。 これらのサービスを必要とするコンポーネント (Razor Pages など) には、コンストラクターのパラメーターを介してこれらのサービスが指定されます。 データベース コンテキスト インスタンスを取得するコンストラクター コードは、この後のチュートリアルで示します。

スキャフォールディング ツールにより、コンテキスト クラスが依存関係挿入コンテナーに自動的に登録されました。

次の強調表示された行がスキャフォールダーによって追加されました。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
}

DbContextOptions オブジェクトでメソッドが呼び出され、接続文字列の名前がコンテキストに渡されます。 ローカル開発の場合、ASP.NET Core 構成システムによって appsettings.json ファイルから接続文字列が読み取られます。

データベース例外フィルターを追加する

次のコードに示されているように、UseMigrationsEndPointAddDatabaseDeveloperPageExceptionFilter を追加します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
       options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));

    services.AddDatabaseDeveloperPageExceptionFilter();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore NuGet パッケージを追加します。

パッケージ マネージャー コンソールで、次のように入力して NuGet パッケージを追加します。

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore NuGet パッケージには Entity Framework Core のエラー ページ用の ASP.NET Core ミドルウェアが用意されています。 このミドルウェアは、Entity Framework Core の移行に関するエラーを検出して診断するのに役立ちます。

により、EF 移行エラーについて、AddDatabaseDeveloperPageExceptionFilter開発環境で役立つエラー情報が提供されます。

データベースの作成

データベースが存在しない場合は、Program.cs を更新して作成します。

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

コンテキストのデータベースが存在する場合、EnsureCreated メソッドは何も実行しません。 データベースが存在しない場合は、データベースとスキーマが作成されます。 EnsureCreated により、データ モデルの変更を処理するための次のワークフローが有効になります。

  • データベースを削除します。 既存のデータはすべて失われます。
  • データ モデルを変更します。 たとえば、EmailAddress フィールドを追加します。
  • アプリを実行します。
  • EnsureCreated により、新しいスキーマを使用してデータベースが作成されます。

このワークフローは、データを保持する必要がない限り、スキーマが急速に進化する開発の早い段階で機能します。 データベースに入力されたデータを保持する必要がある場合は、状況は異なります。 その場合は、移行を使用します。

このチュートリアル シリーズの後半では、EnsureCreated によって作成されたデータベースが削除され、移行が使用されます。 EnsureCreated によって作成されたデータベースは、移行を使用して更新することはできません。

アプリのテスト

  • アプリを実行します。
  • [Students] リンクを選択し、 [新規作成] を選択します。
  • [編集]、[詳細]、および [削除] の各リンクをテストします。

データベースのシード

EnsureCreated メソッドは、空のデータベースを作成します。 このセクションでは、データベースにテスト データを入力するコードを追加します。

次のコードを使用して Data/DbInitializer.cs を作成します。

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

このコードは、データベースに学生が存在するかどうかを確認します。 学生が存在しない場合は、テスト データがデータベースに追加されます。 パフォーマンスを最適化するために、List<T> コレクションではなく配列にテスト データを作成します。

  • Program.cs で、DbInitializer.Initialize 行から // を削除します。

      context.Database.EnsureCreated();
      DbInitializer.Initialize(context);
    
  • アプリが実行されている場合は停止し、パッケージ マネージャー コンソール (PMC) で次のコマンドを実行します。

    Drop-Database -Confirm
    
    
  • Y で応答すると、データベースが削除されます。

  • アプリを再起動します。
  • Students ページを選択すると、シードされたデータが表示されます。

データベースを表示する

  • Visual Studio の [表示] メニューから SQL Server オブジェクト エクスプローラー (SSOX) を開きます。
  • SSOX で、(localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID} の順に選択します。 前に指定したコンテキスト名にダッシュと GUID が追加されて、データベース名が生成されます。
  • [Tables](テーブル) ノードを展開します。
  • [Student](学生) テーブルを右クリックし、 [View Data](データの表示) をクリックすると、作成された列とテーブルに挿入された行が表示されます。
  • Student テーブルを右クリックして [コードの表示] をクリックし、Student モデルが Student テーブル スキーマにどのようにマップされるかを確認します。

非同期コード

ASP.NET Core と EF Core では、非同期プログラミングが既定のモードです。

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

非同期コードが実行時に発生させるオーバーヘッドは少量です。 トラフィックが少ない場合、パフォーマンスに与える影響は無視して構わない程度です。トラフィックが多い場合、相当なパフォーマンス改善が見込まれます。

次のコードでは、キーワード async、戻り値 Task、キーワード await、メソッド ToListAsync によりコードの実行が非同期になります。

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • キーワード async は次のことをコンパイラに指示します。
    • メソッド本文の一部にコールバックを生成する。
    • 返される Task オブジェクトを作成する。
  • 戻り値の型 Task は進行中の作業を表します。
  • キーワード await により、コンパイラはメソッドを 2 つに分割します。 最初の部分は、非同期で開始される操作で終わります。 2 つ目の部分は、操作の完了時に呼び出されるコールバック メソッドに入ります。
  • ToListAsync は、ToList 拡張メソッドの非同期バージョンです。

EF Core を使用する非同期コードを記述するときに注意すべき点:

  • クエリやコマンドをデータベースに送信するステートメントのみが非同期で実行されます。 これには、ToListAsyncSingleOrDefaultAsyncFirstOrDefaultAsyncSaveChangesAsync が含まれます。 var students = context.Students.Where(s => s.LastName == "Davolio") など、IQueryable を変更するだけのステートメントは含まれません。
  • EF Core コンテキストはスレッド セーフではありません。複数の操作を並列実行しないでください。
  • 非同期コードのパフォーマンス上の利点を最大限に活用するには、クエリをデータベースに送信させる EF Core メソッドを (ページングなどのための) ライブラリ パッケージで呼び出す場合、そのライブラリ パッケージで非同期が利用されていることを確認します。

.NET での非同期プログラミングの詳細については、「非同期の概要」と「Async および Await を使用した非同期プログラミング (C#)」を参照してください。

パフォーマンスに関する考慮事項

一般に、Web ページで任意の数の行を読み込むべきではありません。 クエリでは、ページングまたは制限アプローチを使用する必要があります。 たとえば、上記のクエリでは Take を使用して、返される行を制限できます。

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

ビューで大きいテーブルを列挙すると、列挙の途中でデータベース例外が発生した場合、部分的に構築された HTTP 200 応答が返される可能性があります。

MaxModelBindingCollectionSize の既定値は 1024 です。 次のコードでは、MaxModelBindingCollectionSize が設定されます。

public void ConfigureServices(IServiceCollection services)
{
    var myMaxModelBindingCollectionSize = Convert.ToInt32(
                Configuration["MyMaxModelBindingCollectionSize"] ?? "100");

    services.Configure<MvcOptions>(options =>
           options.MaxModelBindingCollectionSize = myMaxModelBindingCollectionSize);

    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
          options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));

    services.AddDatabaseDeveloperPageExceptionFilter();
}

MyMaxModelBindingCollectionSize のような構成設定については、「構成」を参照してください。

ページングについては、このチュートリアルで後述します。

詳細については、「パフォーマンスに関する考慮事項 (EF)」を参照してください。

Entity Framework Core の SQL ログ

一般的に、ログの構成は appsettings.{Environment}.json ファイルの Logging セクションで指定されます。 SQL ステートメントをログに記録するには、appsettings.Development.json ファイルに "Microsoft.EntityFrameworkCore.Database.Command": "Information" を追加します。

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDB-2;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
     ,"Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  },
  "AllowedHosts": "*"
}

上記の JSON では、SQL ステートメントがコマンド ラインと Visual Studio 出力ウィンドウに表示されます。

詳細については、「NET Core と ASP.NET Core でのログ記録」とこの GitHub イシューを参照してください。

次の手順

開発用に SQLite を、運用環境に SQL Server を使用する

これは、ASP.NET Core Razor Pages アプリでの Entity Framework (EF) Core の使用方法を示す一連のチュートリアルの 1 番目です。 このチュートリアルでは、架空の Contoso University の Web サイトを構築します。 サイトには、学生の受け付け、講座の作成、講師の割り当てなどの機能が含まれます。 このチュートリアルでは、コード ファーストのアプローチを使用します。 データベース ファーストのアプローチを使用してこのチュートリアルを実行する方法の詳細については、こちらの Github イシューをご覧ください。

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

前提条件

  • Razor Pages を初めて使用する場合は、このチュートリアルを開始する前に、Razor Pages の概要チュートリアル シリーズをご覧ください。

データベース エンジン

Visual Studio の手順では、SQL Server LocalDB を使用します。これは、Windows 上でのみ実行される SQL Server Express のバージョンです。

Visual Studio Code の手順では、クロスプラットフォーム データベース エンジンである SQLite を使用します。

SQLite の使用を選択した場合は、SQLite データベースを管理および表示するためのサードパーティ製ツール (DB Browser for SQLite など) をダウンロードしてインストールします。

トラブルシューティング

解決できない問題が発生した場合は、コードを完成したプロジェクトと比較します。 ヘルプが必要なときは、ASP.NET Core タグまたは EF Core タグを使用して、StackOverflow.com に質問を投稿することをお勧めします。

サンプル アプリ

このチュートリアル シリーズで作成するアプリは、大学向けの基本的な Web サイトです。 ユーザーは学生、講座、講師の情報を見たり、更新したりできます。 チュートリアルで作成する画面は次のようになります。

Students Index page

Students Edit page

このサイトの UI スタイルは、組み込みのプロジェクト テンプレートに基づいています。 このチュートリアルでは、UI をカスタマイズする方法ではなく、主に EF Core の使用方法について説明します。

ページの上部にあるリンクを使用して、完成したプロジェクトのソース コードを取得します。 cu30 フォルダーには、チュートリアルの ASP.NET Core 3.0 バージョン用のコードが含まれています。 チュートリアル 1-7 のコードの状態を反映するファイルは、cu30snapshots フォルダー内にあります。

完成したプロジェクトをダウンロードした後にアプリを実行するには:

  • プロジェクトをビルドします。

  • パッケージ マネージャー コンソール (PMC) で、次のコマンドを実行します。

    Update-Database
    
  • プロジェクトを実行して、データベースをシードします。

Web アプリプロジェクトを作成する

  • Visual Studio の [ファイル] メニューから、[新規作成]>[プロジェクト] の順に選択します。
  • [ASP.NET Core Web アプリケーション] を選択します。
  • プロジェクトに ContosoUniversity という名前を付けます。 コードをコピーして貼り付けるときに名前空間が一致するように、この正確な名前 (大文字と小文字を含む) を使用することが重要です。
  • ドロップダウン リストで [.NET Core][ASP.NET Core 3.0] を選択してから、 [Web アプリケーション] を選択します。

サイトのスタイルを設定する

Pages/Shared/_Layout.cshtml を更新して、サイト ヘッダー、フッター、およびメニューを設定します。

  • "ContosoUniversity" をすべて "Contoso University" に変更します。 これは 3 回出てきます。

  • Home および Privacy メニュー エントリーを削除し、AboutStudentsCoursesInstructors、および Departments のエントリを追加します。

変更が強調表示されます。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2019 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

Pages/Index.cshtml で、ファイルの中身を次のコードに変更し、ASP.NET Core に関するテキストをこのアプリに関するテキストに変更します。

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

アプリを実行して、ホームページが表示されることを確認します。

データ モデル

以降のセクションでは、データ モデルを作成します。

Course-Enrollment-Student data model diagram

学生は任意の数のコースに登録でき、コースには任意の数の学生を登録できます。

Student エンティティ

Student entity diagram

  • プロジェクト フォルダー内に Models フォルダーを作成します。

  • 次のコードを使用して Models/Student.cs を作成します。

    using System;
    using System.Collections.Generic;
    
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

ID プロパティは、このクラスに相当するデータベース テーブルの主キー列になります。 既定では、EF Core は、ID または classnameID という名前のプロパティを主キーとして解釈します。 そのため、Student クラスの主キーの自動的に認識される代替名は、StudentID です。 詳細については、EF Core のキーに関するページを参照してください。

Enrollments プロパティはナビゲーション プロパティです。 ナビゲーション プロパティには、このエンティティに関連する他のエンティティが含まれます。 この例では、Student エンティティの Enrollments プロパティによって、その Student に関連するすべての Enrollment エンティティが保持されます。 たとえば、データベース内のある Student 行に 2 つの関連する Enrollment 行がある場合、Enrollments ナビゲーション プロパティにその 2 つの Enrollment エンティティが含まれます。

データベースでは、StudentID 列に学生の ID 値が含まれている場合には、Enrollment 行が Student 行に関連付けられます。 たとえば、Student 行の ID が 1 であるとします。 関連する Enrollment 行の StudentID は 1 になります。 StudentID は、Enrollment テーブルの外部キーです。

Enrollments プロパティは、複数の関連する Enrollment エンティティが存在する可能性があるため、ICollection<Enrollment> として定義されます。 他のコレクション型 (List<Enrollment>HashSet<Enrollment> など) を使用できます。 ICollection<Enrollment> を使用する場合、EF Core によって HashSet<Enrollment> コレクションが既定で作成されます。

Enrollment エンティティ

Enrollment entity diagram

次のコードを使用して Models/Enrollment.cs を作成します。

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

EnrollmentID プロパティは主キーです。このエンティティは、ID ではなく classnameID パターンを使用します。 実稼働データ モデルの場合は、1 つのパターンを選択し、一貫してそれを使用します。 このチュートリアルでは、どちらも機能することを示すためだけに両方を使用します。 classname を指定せずに ID を使用すると、一部の種類のデータ モデルの変更の実装が容易になります。

Grade プロパティは enum です。 Grade の型宣言の後の疑問符は、Grade プロパティが nullable であることを示します。 null という成績は、0 の成績とは異なります。null は成績がわからないか、まだ評価されていないことを意味します。

StudentID プロパティは外部キーです。それに対応するナビゲーション プロパティは Student です。 Enrollment エンティティは 1 つの Studentエンティティに関連付けられますので、このプロパティには Student エンティティが 1 つ含まれます。

CourseID プロパティは外部キーです。それに対応するナビゲーション プロパティは Course です。 Enrollment エンティティは 1 つの Course エンティティに関連付けられます。

EF Core は、<navigation property name><primary key property name> という名前のプロパティを外部キーとして解釈します。 たとえば、Student ナビゲーション プロパティの StudentID は外部キーです。Student エンティティの主キーが ID であるからです。 外部キーのプロパティには <primary key property name> という名前を付けることもできます。 たとえば、CourseID にします。Course エンティティの主キーが CourseID であるからです。

Course エンティティ

Course entity diagram

次のコードを使用して Models/Course.cs を作成します。

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Enrollments プロパティはナビゲーション プロパティです。 1 つの Course エンティティにたくさんの Enrollment エンティティを関連付けることができます。

DatabaseGenerated 属性によって、主キーをデータベースに生成させるのではなく、アプリで指定することできます。

プロジェクトをビルドし、コンパイラ エラーがないことを検証します。

Student ページのスキャフォールディング

このセクションでは、ASP.NET Core スキャフォールディング ツールを使用して、次のものを生成します。

  • EF Corecontext クラス。 コンテキストは、定められたデータ モデルに対し、Entity Framework 機能を調整するメイン クラスです。 これは Microsoft.EntityFrameworkCore.DbContext クラスから派生します。
  • Student エンティティの作成、読み取り、更新、および削除 (CRUD) 操作を処理する Razor ページ。
  • Pages フォルダー内に Students フォルダーを作成します。
  • ソリューション エクスプローラーで、[Pages/Students] フォルダーを右クリックし、[追加]>[スキャフォールディングされた新しい項目] の順に選択します。
  • [スキャフォールディングを追加] ダイアログで、[Entity Framework を使用する Razor Pages (CRUD)]>[追加] の順に選択します。
  • [Add Razor Pages using Entity Framework (CRUD)]\(Entity Framework を使用して Razor Pages (CRUD) を追加する\) ダイアログで、次のことを行います。
    • [モデル クラス] ドロップダウンで、 [Student (ContosoUniversity.Models)] を選択します。
    • [データ コンテキスト クラス] 行で、[+] (プラス) 記号を選択します。
    • データ コンテキスト名を ContosoUniversity.Models.ContosoUniversityContext から ContosoUniversity.Data.SchoolContext に変更します。
    • [追加] を選びます。

次のパッケージが自動的にインストールされます。

  • Microsoft.VisualStudio.Web.CodeGeneration.Design
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.Extensions.Logging.Debug
  • Microsoft.EntityFrameworkCore.Tools

前の手順で問題が発生した場合は、プロジェクトをビルドし、スキャフォールディング ステップを再試行してください。

スキャフォールディング プロセスは次のとおりです。

  • Pages/Students フォルダーに Razor ページを作成します。
    • Create.cshtml および Create.cshtml.cs
    • Delete.cshtml および Delete.cshtml.cs
    • Details.cshtml および Details.cshtml.cs
    • Edit.cshtml および Edit.cshtml.cs
    • Index.cshtml および Index.cshtml.cs
  • Data/SchoolContext.cs が作成されます。
  • Startup.cs の依存関係の挿入にコンテキストを追加します。
  • データベース接続文字列を appsettings.json に追加します。

データベース接続文字列

appsettings.json ファイルでは、接続文字列 SQL Server LocalDB が指定されています。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext6;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB は SQL Server Express データベース エンジンの軽量版であり、実稼働ではなく、アプリの開発を意図して設計されています。 既定では、LocalDB は C:/Users/<user> ディレクトリに .mdf ファイルを作成します。

データベース コンテキスト クラスを更新する

定められたデータ モデルの EF Core 機能を調整するメイン クラスは、データベース コンテキスト クラスです。 コンテキストは Microsoft.EntityFrameworkCore.DbContext から派生します。 コンテキストによって、データ モデルに含めるエンティティが指定されます。 このプロジェクトでは、クラスに SchoolContext という名前が付けられています。

次のコードを使用して Data/SchoolContext.cs を更新します。

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

強調表示されたコードによって、各エンティティ セットの DbSet<TEntity> プロパティが作成されます。 EF Core の用語では:

  • エンティティ セットは通常、データベース テーブルに対応します。
  • エンティティはテーブル内の行に対応します。

エンティティ セットには複数のエンティティが含まれているため、DBSet プロパティは複数形の名前にする必要があります。 スキャフォールディング ツールによって Student DBSet を作成したので、このステップでこれを複数形の Students に変更します。

Razor Pages のコードを新しい DBSet 名と一致させるには、プロジェクト全体で _context.Student_context.Students にグローバルに変更します。 8 回の出現があります。

プロジェクトをビルドし、コンパイラ エラーがないことを確認します。

Startup.cs

ASP.NET Core には、依存関係挿入が組み込まれています。 サービス (EF Core データベース コンテキストなど) は、アプリケーションの起動時に依存関係の挿入に登録されます。 これらのサービスを必要とするコンポーネント (Razor Pages など) には、コンストラクターのパラメーターを介してこれらのサービスが指定されます。 データベース コンテキスト インスタンスを取得するコンストラクター コードは、この後のチュートリアルで示します。

スキャフォールディング ツールにより、コンテキスト クラスが依存関係挿入コンテナーに自動的に登録されました。

  • ConfigureServices では、強調表示された行がスキャフォールダーによって追加されました。

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    
        services.AddDbContext<SchoolContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
    }
    

DbContextOptions オブジェクトでメソッドが呼び出され、接続文字列の名前がコンテキストに渡されます。 ローカル開発の場合、ASP.NET Core 構成システムによって appsettings.json ファイルから接続文字列が読み取られます。

データベースの作成

データベースが存在しない場合は、Program.cs を更新して作成します。

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

コンテキストのデータベースが存在する場合、EnsureCreated メソッドは何も実行しません。 データベースが存在しない場合は、データベースとスキーマが作成されます。 EnsureCreated により、データ モデルの変更を処理するための次のワークフローが有効になります。

  • データベースを削除します。 既存のデータはすべて失われます。
  • データ モデルを変更します。 たとえば、EmailAddress フィールドを追加します。
  • アプリを実行します。
  • EnsureCreated により、新しいスキーマを使用してデータベースが作成されます。

このワークフローは、データを保持する必要がない間は、スキーマが急速に進化する開発の初期段階で適切に機能します。 データベースに入力されたデータを保持する必要がある場合は、状況は異なります。 その場合は、移行を使用します。

この後のチュートリアル シリーズでは、EnsureCreated によって作成されたデータベースを削除し、代わりに移行を使用します。 EnsureCreated によって作成されたデータベースは、移行を使用して更新することはできません。

アプリのテスト

  • アプリを実行します。
  • [Students] リンクを選択し、 [新規作成] を選択します。
  • [編集]、[詳細]、および [削除] の各リンクをテストします。

データベースのシード

EnsureCreated メソッドは、空のデータベースを作成します。 このセクションでは、データベースにテスト データを入力するコードを追加します。

次のコードを使用して Data/DbInitializer.cs を作成します。

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

このコードは、データベースに学生が存在するかどうかを確認します。 学生が存在しない場合は、テスト データがデータベースに追加されます。 パフォーマンスを最適化するために、List<T> コレクションではなく配列にテスト データを作成します。

  • Program.cs で、EnsureCreated の呼び出しを DbInitializer.Initialize の呼び出しに置き換えます。

    // context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
    

アプリが実行されている場合は停止し、パッケージ マネージャー コンソール (PMC) で次のコマンドを実行します。

Drop-Database
  • アプリを再起動します。

  • Students ページを選択すると、シードされたデータが表示されます。

データベースを表示する

  • Visual Studio の [表示] メニューから SQL Server オブジェクト エクスプローラー (SSOX) を開きます。
  • SSOX で、(localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID} の順に選択します。 前に指定したコンテキスト名にダッシュと GUID が追加されて、データベース名が生成されます。
  • [Tables](テーブル) ノードを展開します。
  • [Student](学生) テーブルを右クリックし、 [View Data](データの表示) をクリックすると、作成された列とテーブルに挿入された行が表示されます。
  • Student テーブルを右クリックして [コードの表示] をクリックし、Student モデルが Student テーブル スキーマにどのようにマップされるかを確認します。

非同期コード

ASP.NET Core と EF Core では、非同期プログラミングが既定のモードです。

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

非同期コードが実行時に発生させるオーバーヘッドは少量です。 トラフィックが少ない場合、パフォーマンスに与える影響は無視して構わない程度です。トラフィックが多い場合、相当なパフォーマンス改善が見込まれます。

次のコードでは、キーワード async、戻り値 Task<T>、キーワード await、メソッド ToListAsync によりコードの実行が非同期になります。

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • キーワード async は次のことをコンパイラに指示します。
    • メソッド本文の一部にコールバックを生成する。
    • 返される Task オブジェクトを作成する。
  • 戻り値の型 Task<T> は進行中の作業を表します。
  • キーワード await により、コンパイラはメソッドを 2 つに分割します。 最初の部分は、非同期で開始される操作で終わります。 2 つ目の部分は、操作の完了時に呼び出されるコールバック メソッドに入ります。
  • ToListAsync は、ToList 拡張メソッドの非同期バージョンです。

EF Core を使用する非同期コードを記述するときに注意すべき点:

  • クエリやコマンドをデータベースに送信するステートメントのみが非同期で実行されます。 これには、ToListAsyncSingleOrDefaultAsyncFirstOrDefaultAsyncSaveChangesAsync が含まれます。 var students = context.Students.Where(s => s.LastName == "Davolio") など、IQueryable を変更するだけのステートメントは含まれません。
  • EF Core コンテキストはスレッド セーフではありません。複数の操作を並列実行しないでください。
  • 非同期コードのパフォーマンス上の利点を最大限に活用するには、クエリをデータベースに送信させる EF Core メソッドを (ページングなどのための) ライブラリ パッケージで呼び出す場合、そのライブラリ パッケージで非同期が利用されていることを確認します。

.NET での非同期プログラミングの詳細については、「非同期の概要」と「Async および Await を使用した非同期プログラミング (C#)」を参照してください。

次の手順