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

作成者: Tom DykstraRick Anderson

これは、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 インデックス ページ

Students 編集ページ

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

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. [作成] [新しい ASP.NET Core プロジェクト] ダイアログ

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

次のコードをコピーして、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 回出てきます。
  • [ホーム] および [プライバシー] メニュー エントリが削除されます。
  • [バージョン情報][学生][コース][講師] 、および [部門] のエントリが追加されます。

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 からなるデータ モデルの図

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

Student エンティティ

Student エンティティの図

  • プロジェクト フォルダー内に 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 エンティティの図

以下のコードを使用して、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 エンティティの図

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

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

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

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

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

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

データベース接続文字列

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

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

{
  "PageSize": 3,
  "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 ファイルから接続文字列が読み取られます。

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

次のコードに示すように、ConfigureServicesAddDatabaseDeveloperPageExceptionFilter を追加します。

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

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

    services.AddDatabaseDeveloperPageExceptionFilter();
}

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

PMC で、次のように入力して NuGet パッケージを追加します。

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

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

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.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 -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 class IndexModel : PageModel
{
    private readonly SchoolContext _context;
    private readonly MvcOptions _mvcOptions;

    public IndexModel(SchoolContext context, IOptions<MvcOptions> mvcOptions)
    {
        _context = context;
        _mvcOptions = mvcOptions.Value;
    }

    public IList<Student> Student { get;set; }

    public async Task OnGetAsync()
    {
        Student = await _context.Students.Take(
            _mvcOptions.MaxModelBindingCollectionSize).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();
}

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

次の手順

これは、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 インデックス ページ

Students 編集ページ

このサイトの 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 メニューのエントリを削除し、AboutStudentsCoursesInstructorsDepartments のエントリを追加します。

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

<!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 からなるデータ モデルの図

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

Student エンティティ

Student エンティティの図

  • プロジェクト フォルダー内に 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 エンティティの図

以下のコードを使用して、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 エンティティの図

以下のコードを使用して、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 Core コンテキスト クラス。 コンテキストは、定められたデータ モデルに対し、Entity Framework 機能を調整するメイン クラスです。 これは Microsoft.EntityFrameworkCore.DbContext クラスから派生します。
  • Student エンティティの作成、読み取り、更新、および削除 (CRUD) 操作を処理する Razor ページ。
  • Pages フォルダー内に Students フォルダーを作成します。
  • ソリューション エクスプローラー で、Pages/Students フォルダーを右クリックし、 [追加] > [スキャフォールディングされた新しい項目] の順に選択します。
  • [スキャフォールディングを追加] ダイアログで、 [Entity Framework を使用する Razor ページ (CRUD)] > [追加] の順に選択します。
  • [Add Razor Pages using Entity Framework (CRUD)](Entity Framework を使用して Razor Pages (CRUD) を追加する) ダイアログで、次のことを行います。
    • [モデル クラス] ドロップダウンで、 [Student (ContosoUniversity.Models)] を選択します。
    • Data context class 行で、 + (+) 記号を選択します。
    • データ コンテキスト名を 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.cshtmlCreate.cshtml.cs
    • Delete.cshtmlDelete.cshtml.cs
    • Details.cshtmlDetails.cshtml.cs
    • Edit.cshtmlEdit.cshtml.cs
    • Index.cshtmlIndex.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#)」を参照してください。

次の手順

Contoso University のサンプル Web アプリでは、Entity Framework (EF) Core を使用して ASP.NET Core Razor Pages アプリを作成する方法を示しています。

このサンプル アプリは架空の Contoso University の Web サイトです。 学生の受け付け、講座の作成、講師の割り当てなどの機能が含まれています。 このページは、Contoso University のサンプル アプリを作成する方法を説明するチュートリアル シリーズの 1 回目です。

完成したアプリをダウンロードまたは表示します。 ダウンロードの方法はこちらをご覧ください。

必須コンポーネント

Visual Studio 2019 と次のワークロード:

  • ASP.NET および Web の開発
  • .NET Core クロスプラットフォームの開発

Razor Pages に関する知識。 そのプログラミング経験をお持ちでない場合は、このシリーズを始める前に Razor Pages の概要に関するチュートリアルを完了してください。

トラブルシューティング

解決できない問題に遭遇した場合、通常、完成済みのプロジェクトと自分のコードを比較することで解決策がわかります。 ヘルプが必要なときは、StackOverflow.comASP.NET Core または EF Core について質問を投稿することをお勧めします。

Contoso University Web アプリ

このチュートリアル シリーズで作成するアプリは、大学向けの基本的な Web サイトです。

ユーザーは学生、講座、講師の情報を見たり、更新したりできます。 チュートリアルで作成する画面は次のようになります。

Students インデックス ページ

Students 編集ページ

このサイトの UI スタイルは、組み込みのテンプレートによって生成されるものに類似しています。 このチュートリアルでは、UI ではなく、EF Core と Razor ページに重点を置きます。

ContosoUniversity Razor Pages Web アプリを作成する

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

前の手順の画像については、Razor Web アプリの作成に関する記事をご覧ください。 アプリを実行します。

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

変更をいくつか行い、サイトのメニュー、レイアウト、ホーム ページを決めます。 Pages/Shared/_Layout.cshtml に次の変更を加えます。

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

  • メニュー エントリとして「Students」、「Courses」、「Instructors」、「Departments」を追加し、「Contact」を削除します。

変更が強調表示されます。 (マークアップは一部表示されて いません。)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] : Contoso University</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-page="/Index" class="navbar-brand">Contoso University</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-page="/Index">Home</a></li>
                    <li><a asp-page="/About">About</a></li>
                    <li><a asp-page="/Students/Index">Students</a></li>
                    <li><a asp-page="/Courses/Index">Courses</a></li>
                    <li><a asp-page="/Instructors/Index">Instructors</a></li>
                    <li><a asp-page="/Departments/Index">Departments</a></li>
                </ul>
            </div>
        </div>
    </nav>

    <partial name="_CookieConsentPartial" />

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 : Contoso University</p>
        </footer>
    </div>

    @*Remaining markup not shown for brevity.*@

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

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

<div class="jumbotron">
    <h1>Contoso University</h1>
</div>
<div class="row">
    <div class="col-md-4">
        <h2>Welcome to Contoso University</h2>
        <p>
            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 class="col-md-4">
        <h2>Build it from scratch</h2>
        <p>You can build the application by following the steps in a series of tutorials.</p>
        <p>
            <a class="btn btn-default"
               href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro">
                See the tutorial &raquo;
            </a>
        </p>
    </div>
    <div class="col-md-4">
        <h2>Download it</h2>
        <p>You can download the completed project from GitHub.</p>
        <p>
            <a class="btn btn-default"
               href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples/">
                See project source code &raquo;
            </a>
        </p>
    </div>
</div>

データ モデルを作成する

Contoso University アプリのエンティティ クラスを作成します。 次の 3 つのエンティティから始めます。

Course、Enrollment、Student からなるデータ モデルの図

Student エンティティと Enrollment エンティティの間には一対多の関係があります。 Course エンティティと Enrollment エンティティの間には一対多の関係があります。 1 人の学生をさまざまな講座に登録できます。 1 つの講座に任意の数の学生を登録できます。

次のセクションでは、エンティティごとにクラスを作成します。

Student エンティティ

Student エンティティの図

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 プロパティは、このクラスに相当するデータベース (DB) テーブルの主キー列になります。 既定では、EF Core は、ID または classnameID という名前のプロパティを主キーとして解釈します。 classnameIDclassname はクラスの名前です。 代わりの自動的に認識される主キーは、前の例の StudentID です。

Enrollments プロパティはナビゲーション プロパティです。 ナビゲーション プロパティは、このエンティティに関連する他のエンティティにリンクします。 この例では、Student entityEnrollments プロパティによって、その Student に関連するすべての Enrollment エンティティが保持されます。 たとえば、DB 内のある Student 行に 2 つ関連する Enrollment 行がある場合、Enrollments ナビゲーション プロパティにその 2 つの Enrollment エンティティが含まれます。 関連する Enrollment 行とは、その学生の主キー値を StudentID 列に含む行です。 たとえば、ID=1 の学生には、Enrollment テーブルに行が 2 つあるとします。 その Enrollment テーブルには、StudentID = 1 の行が 2 つあります。 StudentIDEnrollment テーブルの外部キーであり、Student テーブルでその学生を指定します。

あるナビゲーション プロパティが複数のエンティティを保持できる場合、そのナビゲーション プロパティは ICollection<T> のようなリスト型にする必要があります。 ICollection<T> を指定するか、List<T>HashSet<T> のような型を指定できます。 ICollection<T> を使用する場合、EF Core で HashSet<T> コレクションが既定で作成されます。 複数のエンティティを保持するナビゲーション プロパティは、多対多または一対多の関係から発生します。

Enrollment エンティティ

Enrollment エンティティの図

以下のコードを使用して、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 プロパティは主キーです。 このエンティティでは、Student エンティティのような ID ではなく、classnameID パターンを使用します。 一般的に、開発者は 1 つのパターンを選択し、データ モデル全体でそれを使用します。 後のチュートリアルでは、クラス名なしの ID を利用し、データ モデルに継承を簡単に実装します。

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

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

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 エンティティの図

以下のコードを使用して、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 属性によって、アプリで主キーを指定できます。DB に生成させるのではありません。

Student モデルをスキャホールディングする

このセクションでは、Student モデルがスキャフォールディングされます。 つまり、スキャフォールディング ツールにより、Student モデルの作成、読み取り、更新、削除の (CRUD) 操作用のページが生成されます。

  • プロジェクトをビルドします。
  • Pages/Students フォルダーを作成します。
  • ソリューション エクスプローラー で、Pages/Students フォルダーを右クリックし、 [追加] > [スキャフォールディングされた新しい項目] の順に選択します。
  • [スキャフォールディングを追加] ダイアログで、 [Entity Framework を使用する Razor ページ (CRUD)] > [追加] の順に選択します。

[Add Razor Pages using Entity Framework (CRUD)](Entity Framework を使用して Razor Pages (CRUD) を追加する) ダイアログを完了します。

  • [モデル クラス] ドロップダウンで、 [Student (ContosoUniversity.Models)] を選択します。
  • [データ コンテキスト クラス] 行で + (+) 記号を選択し、生成された名前を ContosoUniversity.Models.SchoolContext に変更します。
  • [データ コンテキスト クラス] ドロップダウンで、 [ContosoUniversity.Models.SchoolContext] を選択します。
  • [追加] を選びます。

CRUD ダイアログ

前の手順に問題がある場合は、「ムービー モデルのスキャフォールディング」を参照してください。

スキャフォールディングのプロセスが作成され、次のファイルが変更されます。

作成されたファイル

  • Pages/Students 作成、削除、詳細、編集、インデックス。
  • Data/SchoolContext.cs

ファイルの更新

  • Startup.cs:このファイルに対する変更を次のセクションで詳しく説明します。
  • appsettings.json :ローカル データベースへの接続に使用される接続文字列を追加します。

依存関係挿入に登録されるコンテキストを調べる

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

スキャフォールディング ツールが自動的に DB コンテキストを作成し、依存関係挿入コンテナーと共に登録します。

Startup.csConfigureServices メソッドを調べます。 強調表示された行は、スキャフォールダーによって追加されました。

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for 
        //non -essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

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

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

main を更新する

Program.cs で、次を実行するように Main メソッドを変更します。

  • 依存関係挿入コンテナーから DB コンテキスト インスタンスを取得します。
  • EnsureCreated を呼び出します。
  • EnsureCreated メソッドが完了したら、コンテキストを破棄します。

次は、更新された Program.cs ファイルのコードです。

using ContosoUniversity.Models;                   // SchoolContext
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;   // CreateScope
using Microsoft.Extensions.Logging;
using System;

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

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

                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }

            host.Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

EnsureCreated で、コンテキストのデータベースが存在することを確認します。 存在する場合、アクションは何も実行されません。 存在しない場合は、データベースとそのすべてのスキーマが作成されます。 EnsureCreated は、データベースの作成に移行を使用しません。 EnsureCreated で作成されたデータベースは、後で移行を使用して更新することはできません。

アプリの起動時に EnsureCreated が呼び出され、次のワークフローが可能になります。

  • DB を削除します。
  • DB スキーマを変更します (たとえば、EmailAddress フィールドを追加します)。
  • アプリを実行します。
  • EnsureCreated によって、EmailAddress 列を含む DB が作成されます。

EnsureCreated は、スキーマが急速に進化している開発の早期に便利です。 このチュートリアルの後半では、DB は削除され、移行が使用されます。

アプリのテスト

アプリを実行し、cookie ポリシーに同意します。 このアプリで個人情報が保持されることはありません。 cookie ポリシーについては、EU の一般的なデータ保護規制 (GDPR) のサポートに関するページを参照してください。

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

SchoolContext DB コンテキストを確認する

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

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

using Microsoft.EntityFrameworkCore;

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

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

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

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

DbSet<Enrollment>DbSet<Course> は省略可能です。 EF Core にはそれらが暗黙的に含まれます。Student エンティティが Enrollment エンティティを参照し、Enrollment エンティティが Course エンティティを参照するためです。 このチュートリアルでは、SchoolContextDbSet<Enrollment>DbSet<Course> を残します。

SQL Server Express LocalDB

この接続文字列によって SQL Server LocalDB が指定されます。 LocalDB は SQL Server Express データベース エンジンの軽量版であり、実稼働ではなく、アプリの開発を意図して設計されています。 LocalDB は要求時に開始され、ユーザー モードで実行されるため、複雑な構成はありません。 既定では、LocalDB は C:/Users/<user> ディレクトリに .mdf DB ファイルを作成します。

テスト データで DB を初期化するコードを追加する

EF Core によって空の DB が作成されます。 このセクションでは、それにテスト データを入力するために Initialize メソッドを記述します。

Data フォルダーで、DbInitializer.cs という名前の新しいクラス ファイルを作成し、次のコードを追加します。

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

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

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

            var students = new Student[]
            {
            new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
            new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
            new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
            };
            foreach (Student s in students)
            {
                context.Student.Add(s);
            }
            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}
            };
            foreach (Course c in courses)
            {
                context.Course.Add(c);
            }
            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},
            };
            foreach (Enrollment e in enrollments)
            {
                context.Enrollment.Add(e);
            }
            context.SaveChanges();
        }
    }
}

メモ:上のコードでは、名前空間 (namespace ContosoUniversity.Models) に Data ではなく Models を使用しています。 Models はスキャフォールディング機能によって生成されたコードと一致します。 詳細については、こちらの GitHub のスキャフォールディングの問題に関するページを参照してください。

このコードは、DB に学生が存在するかどうかを確認します。 DB に学生が存在しない場合、テスト データを使用して DB が初期化されます。 List<T> コレクションではなく配列にテスト データを読み込み、パフォーマンスを最適化します。

EnsureCreated メソッドは、DB のコンテキストに合わせて DB を自動的に作成します。 DB が存在する場合、EnsureCreated は DB を変更することなく戻ってきます。

Program.cs で、Initialize を呼び出すように Main メソッドを変更します。

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

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

            try
            {
                var context = services.GetRequiredService<SchoolContext>();
                // using ContosoUniversity.Data; 
                DbInitializer.Initialize(context);
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred creating the DB.");
            }
        }

        host.Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

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

Drop-Database

DB を表示する

前に指定したコンテキスト名にダッシュと GUID が追加されて、データベース名が生成されます。 したがって、データベース名は "SchoolContext-{GUID}" になります。 GUID は、ユーザーごとに異なります。 Visual Studio の [表示] メニューから SQL Server オブジェクト エクスプローラー (SSOX) を開きます。 SSOX で、 (localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID} をクリックします。

[Tables](テーブル) ノードを展開します。

[Student](学生) テーブルを右クリックし、 [View Data](データの表示) をクリックすると、作成された列とテーブルに挿入された行が表示されます。

非同期コード

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

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

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

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

public async Task OnGetAsync()
{
    Student = await _context.Student.ToListAsync();
}
  • キーワード async は次のことをコンパイラに指示します。

    • メソッド本文の一部にコールバックを生成する。
    • 返された Task オブジェクトを自動作成する。 詳細については、Task の戻り値の型に関する記事を参照してください。
  • 暗黙の戻り値の型 Task は進行中の作業を表します。

  • キーワード await により、コンパイラはメソッドを 2 つに分割します。 最初の部分は、非同期で開始される操作で終わります。 2 つ目の部分は、操作の完了時に呼び出されるコールバック メソッドに入ります。

  • ToListAsync は、ToList 拡張メソッドの非同期バージョンです。

EF Core を利用する非同期コードの記述で注意すべき点:

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

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

次のチュートリアルでは、基本的な CRUD (作成、読み取り、更新、削除) の操作について説明します。

その他の技術情報