events
チュートリアル: ASP.NET MVC Web アプリでの EF Core の概要
作成者: Tom Dykstra、Rick Anderson
このチュートリアルでは、ASP.NET Core MVC と Entity Framework Core のコントローラーとビューについて説明します。 Razor Pages は代替プログラミング モデルです。 新しい開発では、コントローラーやビューを使う MVC よりも Razor Pages を使うことをお勧めします。 このチュートリアルの Razor Pages バージョンを参照してください。 それぞれのチュートリアルには、もう一方では説明されない内容が含まれています。
この MVC のチュートリアルに含まれ、Razor Pages のチュートリアルには含まれていないこと:
- データ モデルで継承を実装する
- 生 SQL クエリを実行する
- 動的な LINQ を使ってコードを簡略化する
Razor Pages のチュートリアルに含まれ、このチュートリアルには含まれていないこと:
- Select メソッドを使って関連データを読み込む
- EF のベスト プラクティス。
Contoso University のサンプル Web アプリでは、Entity Framework (EF) Core と Visual Studio を使用して ASP.NET Core MVC Web アプリを作成する方法を示します。
このサンプル アプリは架空の Contoso University の Web サイトです。 学生の受け付け、講座の作成、講師の割り当てなどの機能が含まれています。 これは、Contoso University のサンプル アプリを作成する方法を説明するチュートリアル シリーズの 1 回目です。
- ASP.NET Core MVC を初めて使用する場合は、これを開始する前に、「ASP.NET Core MVC の概要」チュートリアル シリーズをご覧ください。
- Visual Studio 2022 と ASP.NET と Web 開発ワークロード。
- .NET 6.0 SDK
このチュートリアルは、ASP.NET Core 6 以降用に更新されていません。 チュートリアルの手順は、ASP.NET Core 6 以降を対象とするプロジェクトを作成した場合は正しく動作しません。 たとえば、ASP.NET Core 6 以降の Web テンプレートでは、Startup.cs
と Program.cs
が 1 つの Program.cs
ファイルに統合された最小ホスティング モデルが使われます。
.NET 6 で導入されたもう 1 つの違いは、NRT (null 参照型) 機能です。 この機能は、プロジェクト テンプレートで既定で有効になります。 .NET 5 では null 許容のプロパティが.NET 6 で必須であると EF によって判断された場合、問題が発生する可能性があります。 たとえば、Enrollments
プロパティを null 許容にするか、asp-validation-summary
ヘルパー タグを ModelOnly
から All
に変更しない限り、[学生の作成] ページは警告なしで失敗します。
このチュートリアルでは、.NET 5 SDK をインストールして使用することをお勧めします。 ASP.NET Core 6 以降で Entity Framework を使用する方法については、このチュートリアルが更新されるまでは「RazorASP.NET Core での Entity Framework Core を使用した Razor Pages - チュートリアル 1/8」を参照してください。
Visual Studio の手順では、SQL Server LocalDB を使用します。これは、Windows 上でのみ実行される SQL Server Express のバージョンです。
解決できない問題に遭遇した場合、通常、完成済みのプロジェクトと自分のコードを比較することで解決策がわかります。 一般的なエラーとその解決方法の一覧については、このシリーズの最後のチュートリアルにあるトラブルシューティングのセクションをご覧ください。 そこで必要な答えが見つからない場合、StackOverflow.com で ASP.NET Core または EF Core に関する質問を投稿できます。
ヒント
これは 10 回のチュートリアルからなるシリーズであり、いずれの回も前のチュートリアルを基盤にしています。 チュートリアルが完了したら、毎回、プロジェクトのコピーを保存するようお勧めします。 問題に遭遇したとき、前のチュートリアルから始めることができます。シリーズ全体の始めまで戻る必要がありません。
このチュートリアル シリーズで作成するアプリは、大学向けの基本的な Web サイトです。
ユーザーは学生、講座、講師の情報を見たり、更新したりできます。 アプリの画面のいくつかを次に示します。
- Visual Studio を開始し、 [新しいプロジェクトの作成] を選択します。
- [新しいプロジェクトの作成] ダイアログで、[ASP.NET Core Web アプリケーション]>[次へ] の順に選択します。
- [新しいプロジェクトの構成] ダイアログで、 [プロジェクト名] に「
ContosoUniversity
」と入力します。 コードをコピーするときに各namespace
が一致するように、この正確な名前 (大文字と小文字を含む) を使用することが重要です。 - [作成] を選択します
- [新しい ASP.NET Core Web アプリケーションの作成] ダイアログで、次のものを選択します。
- ドロップダウンで [.NET Core] と [ASP.NET Core 5.0]
- ASP.NET Core Web アプリ (Model-View-Controller)
- 作成
基本的な変更をいくつか行い、サイトのメニュー、レイアウト、home ページを決めます。
Views/Shared/_Layout.cshtml
を開き、次の変更を行います。
- 各
ContosoUniversity
をContoso University
に変更します。 これは 3 回出てきます。 - メニュー エントリとして「About」、「Students」、「Courses」、「Instructors」、「Departments」を追加し、「Privacy」メニュー エントリを削除します。
次のコードでは上の変更が強調表示されています。
<!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-controller="Home" asp-action="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 justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="About">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Courses" asp-action="Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Departments" asp-action="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">
© 2020 - Contoso University - <a asp-area="" asp-controller="Home" asp-action="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>
Views/Home/Index.cshtml
で、ファイルの内容を次のマークアップに置き換えます。
@{
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 MVC web application.
</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.asp.net/en/latest/data/ef-mvc/intro.html">See the tutorial »</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-mvc/intro/samples/5cu-final">See project source code »</a></p>
</div>
</div>
CTRL を押しながら F5 を押してプロジェクトを実行するか、メニューで [デバッグ] > [デバッグなしで開始] の順に選択します。 home ページに、このチュートリアルで作成されるページのタブが表示されます。
このチュートリアルでは SQL Server を使用します。プロバイダー パッケージは Microsoft.EntityFrameworkCore.SqlServer です。
EF SQL Server パッケージとその依存関係 Microsoft.EntityFrameworkCore
と Microsoft.EntityFrameworkCore.Relational
により、EF のランタイム サポートが提供されます。
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore NuGet パッケージを追加します。 パッケージ マネージャー コンソール (PMC) で、次のコマンドを入力して NuGet パッケージを追加します。
Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
NuGet パッケージには、EF Core のエラー ページ用の ASP.NET Core ミドルウェアが用意されています。 このミドルウェアは、EF Core の移行に関するエラーを検出して診断するのに役立ちます。
EF Core で利用できるその他のデータベース プロバイダーに関しては、「データベース プロバイダー」を参照してください。
このアプリ用に次のエンティティ クラスを作成します。
上のエンティティには、次のリレーションシップがあります。
Student
エンティティとEnrollment
エンティティの間の一対多リレーションシップ。 1 人の学生を任意の数の講座に登録できます。Course
エンティティとEnrollment
エンティティの間の一対多リレーションシップ。 1 つの講座に任意の数の学生を登録できます。
次のセクションでは、これらの各エンティティに対してクラスを作成します。
以下のコードを使用して、Models フォルダーに Student
クラスを作成します。
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
プロパティは、このクラスに相当するデータベース テーブルの主キー (PK) 列です。 既定では、EF は、ID
または classnameID
という名前のプロパティを主キーとして解釈します。 たとえば、PK には ID
ではなく StudentID
という名前を付けることができます。
Enrollments
プロパティはナビゲーション プロパティです。 ナビゲーション プロパティには、このエンティティに関連する他のエンティティが含まれます。 Student
エンティティの Enrollments
プロパティ:
- その
Student
エンティティに関連するすべてのEnrollment
エンティティが含まれます。 - データベースの特定の
Student
行に、関連するEnrollment
行が 2 つある場合:- その
Student
エンティティのEnrollments
ナビゲーション プロパティには、これら 2 つのEnrollment
エンティティが含まれます。
- その
Enrollment
行には、StudentID
外部キー (FK) 列の学生の PK 値が含まれます。
ナビゲーション プロパティが複数のエンティティを保持できる場合:
- 型は、
ICollection<T>
、List<T>
、HashSet<T>
などのリストである必要があります。 - エンティティの追加、削除、更新を行うことができます。
多対多および一対多のナビゲーション リレーションシップは、複数のエンティティを含むことができます。 ICollection<T>
を使用する場合、EF によって HashSet<T>
コレクションが既定で作成されます。
以下のコードを使用して、Models フォルダーに Enrollment
クラスを作成します。
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
プロパティは PK です。 このエンティティには、ID
自体ではなく classnameID
パターンが使用されます。 Student
エンティティには ID
パターンが使用されていました。 データ モデル全体で 1 つのパターンを使用することを好む開発者もいます。 このチュートリアルのバリエーションでは、どちらのパターンも使用できることが示されています。 後のチュートリアルでは、クラス名なしの ID
を使用して、データ モデルに継承を簡単に実装する方法を示します。
Grade
プロパティは enum
です。 Grade
の型宣言の後にある ?
は、Grade
プロパティが Null 許容であることを示します。 null
という成績は、成績が 0 であるということではありません。 null
は、成績がわからないこと、またはまだ割り当てられていないことを意味します。
StudentID
プロパティは外部キー (FK) であり、それに対応するナビゲーション プロパティは Student
です。 Enrollment
エンティティは 1 つの Student
エンティティに関連付けられるので、プロパティで保持できる Student
エンティティは 1 つだけです。 これは、複数の Enrollment
エンティティを保持できる Student.Enrollments
ナビゲーション プロパティとは異なります。
CourseID
プロパティは FK で、それに対応するナビゲーション プロパティは Course
です。 Enrollment
エンティティは 1 つの Course
エンティティに関連付けられます。
プロパティの名前が <
ナビゲーション プロパティ名><
主キー プロパティ名>
になっている場合、それは Entity Framework により FK プロパティとして解釈されます。 たとえば、Student
ナビゲーション プロパティに対する StudentID
(Student
エンティティの PK は ID
であるため)。 FK プロパティは、<
主キー プロパティ名>
という名前にすることもできます。 たとえば、CourseID
(Course
エンティティの PK が CourseID
であるため)。
以下のコードを使用して、Models フォルダーに Course
クラスを作成します。
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 属性については、後のチュートリアルで説明されています。 この属性を使用すると、講座の PK を、データベースによって生成するのではなく、自分で入力できます。
定められたデータ モデルの EF 機能を調整するメイン クラスは、DbContext データベース コンテキスト クラスです。 このクラスは Microsoft.EntityFrameworkCore.DbContext
クラスから派生させて作成します。 DbContext
派生クラスによって、データ モデルに含めるエンティティが指定されます。 EF の一部の動作はカスタマイズできます。 このプロジェクトでは、クラスに SchoolContext
という名前が付けられています。
プロジェクト フォルダーで、Data
という名前のフォルダーを作成します。
Data フォルダーに、次のコードで SchoolContext
クラスを作成します。
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
}
}
上記のコードにより、各エンティティ セットに対する DbSet
プロパティが作成されます。 EF の用語では:
- エンティティ セットは通常、データベース テーブルに対応します。
- エンティティはテーブル内の行に対応します。
DbSet<Enrollment>
ステートメントと DbSet<Course>
ステートメントは省略することができ、動作は同じです。 EF には、次の理由により暗黙的にこれらが含まれます。
Student
エンティティでは、Enrollment
エンティティが参照されます。Enrollment
エンティティでは、Course
エンティティが参照されます。
データベースが作成されると、EF によって、DbSet
プロパティと同じ名前を持つテーブルが作成されます。 コレクションのプロパティ名は通常、複数形になります。 たとえば、Student
ではなく Students
。 テーブル名を複数形にするかどうかについては、開発者の間で意見が分かれるでしょう。 これらのチュートリアルでは、DbContext
で単数形のテーブル名を指定することにより、既定の動作をオーバーライドします。 そのために、最後の DbSet プロパティの後に、次の強調表示されているコードを追加します。
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}
ASP.NET Core には、依存関係挿入が含まれています。 EF データベース コンテキストなどのサービスは、アプリの起動時に依存関係の挿入で登録されます。 これらのサービス (MVC コント ローラーなど) を必要とするコンポーネントには、コンストラクターのパラメーターを介してこれらのサービスが提供されます。 コンテキスト インスタンスを取得するコントローラー コンストラクターのコードは、このチュートリアルで後ほど示します。
SchoolContext
をサービスとして登録するには、Startup.cs
を開き、強調表示されている行を ConfigureServices
メソッドに追加します。
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace ContosoUniversity
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllersWithViews();
}
DbContextOptionsBuilder
オブジェクトでメソッドが呼び出され、接続文字列の名前がコンテキストに渡されます。 ローカル開発の場合、ASP.NET Core 構成システムによって appsettings.json
ファイルから接続文字列が読み取られます。
appsettings.json
ファイルを開き、次のマークアップで示されているように接続文字列を追加します。
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
次のコードに示すように、ConfigureServices
に AddDatabaseDeveloperPageExceptionFilter を追加します。
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddControllersWithViews();
}
AddDatabaseDeveloperPageExceptionFilter
により、開発環境で役に立つエラー情報が提供されます。
この接続文字列によって SQL Server LocalDB が指定されます。 LocalDB は SQL Server Express データベース エンジンの軽量版であり、実稼働ではなく、アプリの開発を意図して設計されています。 LocalDB は要求時に開始され、ユーザー モードで実行されるため、複雑な構成はありません。 既定では、LocalDB は C:/Users/<user>
ディレクトリに .mdf DB ファイルを作成します。
EF により、空のデータベースが作成されます。 このセクションでは、データベースが作成された後でそれにテスト データを設定するために呼び出されるメソッドを追加します。
EnsureCreated
メソッドを使用して、データベースを自動的に作成します。 後のチュートリアルでは、モデル変更の処理方法について説明します。データベースを削除し、再作成するのではなく、Code First Migrations を利用してデータベース スキーマを変更します。
Data フォルダーに、次のコードで DbInitializer
という名前の新しいクラスを作成します。
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("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.Students.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.Courses.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.Enrollments.Add(e);
}
context.SaveChanges();
}
}
}
上記のコードでは、データベースが存在するかどうかを確認します。
- データベースが見つからない場合
- 作成されて、テスト データが読み込まれます。
List<T>
コレクションではなく配列にテスト データを読み込み、パフォーマンスを最適化します。
- 作成されて、テスト データが読み込まれます。
- データベースが見つかった場合は、何も行われません。
次のコードを使用して 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>();
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>();
});
}
}
アプリの起動時に、Program.cs
によって次のことが行われます。
- 依存関係挿入コンテナーからデータベース コンテキスト インスタンスを取得します。
DbInitializer.Initialize
メソッドを呼び出します。- 次のコードで示すように、
Initialize
メソッドが完了したらコンテキストを破棄します。
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>();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
host.Run();
}
アプリが初めて実行されたときは、データベースが作成され、テスト データが読み込まれます。 データ モデルが変更されるたび:
- データベースを削除します。
- シード メソッドを更新し、新しいデータベースで新たに開始します。
後のチュートリアルでは、データ モデルが変更されたら、データベースを削除して作成し直すのではなく、それを更新します。 データ モデルが変更されても、データは失われません。
Visual Studio のスキャフォールディング エンジンを使用して、MVC コントローラーとビューを追加します。これらにより、EF を使用してクエリが実行され、データが保存されます。
CRUD アクションのメソッドとビューの自動作成は、スキャフォールディングと呼ばれます。
- ソリューション エクスプローラーで、
Controllers
フォルダーを右クリックし、[追加] > [新規スキャフォールディング アイテム] を選択します。 - [スキャフォールディングを追加] ダイアログ ボックスで:
- [Entity Framework を使用したビューがある MVC コントローラー] を選択します。
- [追加] をクリックします。 [Entity Framework を使用してビューがある MVC コントローラーを追加する] ダイアログ ボックスが表示されます。
- [モデル クラス] で Student を選択します。
- [データ コンテキスト クラス] で SchoolContext を選択します。
- 名前は StudentsController をそのまま選択します。
- [追加] をクリックします。
Visual Studio スキャフォールディング エンジンにより、StudentsController.cs
ファイルと、コントローラーで動作するビューのセット (*.cshtml
ファイル) が作成されます。
コントローラーによりコンストラクター パラメーターとして SchoolContext
が受け取られることに注意してください。
namespace ContosoUniversity.Controllers
{
public class StudentsController : Controller
{
private readonly SchoolContext _context;
public StudentsController(SchoolContext context)
{
_context = context;
}
ASP.NET Core 依存関係挿入では、SchoolContext
のインスタンスがコントローラーに渡されます。 Startup
クラスでそれを構成しました。
コントローラーには Index
アクション メソッドが含まれます。これはデータベースにあるすべての学生を表示します。 このメソッドはデータベース コンテキスト インスタンスの Students
プロパティを読み取り、Students エンティティ セットから学生の一覧を取得します。
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}
このコードの非同期プログラミング要素については、チュートリアルで後ほど説明します。
Views/Students/Index.cshtml
ビューには、この一覧で表形式で表示されます。
@model IEnumerable<ContosoUniversity.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
CTRL を押しながら F5 を押してプロジェクトを実行するか、メニューで [デバッグ] > [デバッグなしで開始] の順に選択します。
[Students] タブをクリックすると、DbInitializer.Initialize
メソッドによって挿入されたテスト データが表示されます。 ブラウザーのウィンドウ幅によって決まることですが、Students
タブ リンクはページの一番上に表示されるか、右上隅のナビゲーション アイコンをクリックしないと表示されません。
アプリが起動すると、DbInitializer.Initialize
メソッドによって EnsureCreated
が呼び出されます。 EF により、データベースが存在しないことが認識されました。
- そのため、データベースが作成されました。
Initialize
メソッドのコードにより、データベースにデータが設定されました。
Visual Studio でデータベースを表示するには、SQL Server オブジェクト エクスプローラー (SSOX) を使用します。
- Visual Studio の [表示] メニューで [SQL Server オブジェクト エクスプローラー] を選択します。
- SSOX で、(localdb)\MSSQLLocalDB > [データベース] を選択します。
ContosoUniversity1
を選択します。これは、appsettings.json
ファイル内の接続文字列に含まれるデータベース名のエントリです。- [テーブル] ノードを展開し、データベースのテーブルを表示します。
Student テーブルを右クリックし、 [データの表示] をクリックして、テーブル内のデータを表示します。
*.mdf
および *.ldf
データベース ファイルは、C:\Users\<username> フォルダーにあります。
アプリの起動時に実行される初期化メソッドで EnsureCreated
が呼び出されるため、次のことができます。
Student
クラスに対する変更を行います。- データベースを削除します。
- アプリを停止してから開始します。 変更に合わせてデータベースが自動的に再作成されます。
たとえば、Student
クラスに EmailAddress
プロパティが追加された場合、再作成されたテーブルには新しい EmailAddress
列が含まれます。 ビューには、新しい EmailAddress
プロパティは表示されません。
EF によって使用される規約のため、EF で完全なデータベースが作成されるために記述するコードの量は最小限に抑えられます。
DbSet
プロパティの名前がテーブル名として使用されます。DbSet
プロパティによって参照されないエンティティについては、エンティティ クラス名がテーブル名として使用されます。- 列名には、エンティティ プロパティ名が使用されます。
ID
またはclassnameID
という名前のエンティティ プロパティは、PK プロパティとして認識されます。- 名前が
<
ナビゲーション プロパティ名><
PK プロパティ名>
であるプロパティは、FK プロパティとして解釈されます。 たとえば、Student
ナビゲーション プロパティに対するStudentID
(Student
エンティティの PK はID
であるため)。 FK プロパティは、<
主キー プロパティ名>
という名前にすることもできます。 たとえば、EnrollmentID
(Enrollment
エンティティの PK がEnrollmentID
であるため)。
従来の動作をオーバーライドできます。 たとえば、このチュートリアルで先に示したように、テーブル名を明示的に指定できます。 列名と任意のプロパティを、PK または FK として設定できます。
ASP.NET Core と EF Core では、非同期プログラミングが既定のモードです。
Web サーバーでは、利用できるスレッド数に限りがあります。負荷が高い状況では、利用できるスレッドが全部使われる可能性があります。 その場合、スレッドが解放されるまでサーバーは新しい要求を処理できません。 同期コードの場合、たくさんのスレッドが関連付けられていても、I/O の完了を待っているため、実際には何の作業も行っていないということがあります。 非同期コードの場合、あるプロセスが I/O の完了を待っているとき、そのスレッドは解放され、サーバーによって他の要求の処理に利用できます。 結果として、非同期コードの場合、サーバー リソースをより効率的に利用できます。サーバーは、より多くのトラフィックを遅延なく処理できます。
非同期コードは実行時に少量のオーバーヘッドを発生させるが、トラフィックが少ない場合、パフォーマンスに与える影響は無視して構わない程度です。トラフィックが多い場合、相当なパフォーマンス改善が見込まれます。
次のコードでは、async
、Task<T>
、await
、ToListAsync
により、コードは非同期的に実行されます。
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}
- キーワード
async
は、メソッド本文の一部にコールバックを生成し、返されたTask<IActionResult>
オブジェクトを自動作成するようにコンパイラに伝えます。 - 戻り値の型
Task<IActionResult>
は、進行中の作業と型IActionResult
の結果を表します。 - キーワード
await
により、コンパイラはメソッドを 2 つに分割します。 最初の部分は、非同期で開始される操作で終わります。 2 つ目の部分は、操作の完了時に呼び出されるコールバック メソッドに入ります。 ToListAsync
は、ToList
拡張メソッドの非同期バージョンです。
EF を使用する非同期コードを記述するときに注意すべき点:
- クエリやコマンドをデータベースに送信するステートメントのみが非同期で実行されます。 たとえば、
ToListAsync
、SingleOrDefaultAsync
、SaveChangesAsync
などです。var students = context.Students.Where(s => s.LastName == "Davolio")
など、IQueryable
を変更するだけのステートメントは含まれません。 - EF コンテキストはスレッド セーフではありません。複数の操作を並列実行しないでください。 非同期 EF メソッドを呼び出すとき、
await
キーワードを常に使用します。 - 非同期コードのパフォーマンス上の利点を利用するには、使用されているライブラリ パッケージで、クエリがデータベースに送信される EF のメソッドが呼び出されている場合は、それらでも非同期を使用するようにします。
.NET の非同期プログラミングについては、「非同期の概要」を参照してください。
クエリから返されるエンティティの数の制限については、「パフォーマンスに関する考慮事項」を参照してください。
一般的に、ログの構成は 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 イシューを参照してください。
基本的な CRUD (作成、読み取り、更新、削除) 操作の実行方法を学習するには、次のチュートリアルに進んでください。
このチュートリアルでは、ASP.NET Core MVC と Entity Framework Core のコントローラーとビューについて説明します。 Razor Pages は代替プログラミング モデルです。 新しい開発では、コントローラーやビューを使う MVC よりも Razor Pages を使うことをお勧めします。 このチュートリアルの Razor Pages バージョンを参照してください。 それぞれのチュートリアルには、もう一方では説明されない内容が含まれています。
この MVC のチュートリアルに含まれ、Razor Pages のチュートリアルには含まれていないこと:
- データ モデルで継承を実装する
- 生 SQL クエリを実行する
- 動的な LINQ を使ってコードを簡略化する
Razor Pages のチュートリアルに含まれ、このチュートリアルには含まれていないこと:
- Select メソッドを使って関連データを読み込む
- EF のベスト プラクティス。
Contoso University のサンプル Web アプリケーションでは、Entity Framework (EF) Core 2.2 と Visual Studio 2019 を使用して ASP.NET Core 2.2 MVC Web アプリケーションを作成する方法を示します。
このチュートリアルは、ASP.NET Core 3.1 用に更新されていません。 ASP.NET Core 5.0 用に更新されています。
サンプル アプリケーションは架空の Contoso University の Web サイトです。 学生の受け付け、講座の作成、講師の割り当てなどの機能が含まれています。 これは、Contoso University のサンプル アプリケーションを一から作成する方法を説明するチュートリアル シリーズの 1 回目です。
- .NET Core SDK 2.2
- Visual Studio 2019 と次のワークロード:
- ASP.NET および Web の開発ワークロード
- .NET Core クロスプラットフォームの開発ワークロード
解決できない問題に遭遇した場合、通常、完成済みのプロジェクトと自分のコードを比較することで解決策がわかります。 一般的なエラーとその解決方法の一覧については、このシリーズの最後のチュートリアルにあるトラブルシューティングのセクションをご覧ください。 そこで必要な答えが見つからない場合、StackOverflow.com で ASP.NET Core または EF Core に関する質問を投稿できます。
ヒント
これは 10 回のチュートリアルからなるシリーズであり、いずれの回も前のチュートリアルを基盤にしています。 チュートリアルが完了したら、毎回、プロジェクトのコピーを保存するようお勧めします。 問題に遭遇したとき、前のチュートリアルから始めることができます。シリーズ全体の始めまで戻る必要がありません。
一連のチュートリアルで作成するアプリケーションは、簡単な大学向け Web サイトです。
ユーザーは学生、講座、講師の情報を見たり、更新したりできます。 次のような画面をこれから作成します。
Visual Studio を開きます。
[ファイル] メニューで、 [新規作成] > [プロジェクト] の順に選びます。
左側のウィンドウで、[インストール済み] > [Visual C#] > [Web] の順に選択します。
[ASP.NET Core Web アプリケーション] プロジェクト テンプレートを選択します。
名前に「ContosoUniversity」と入力し、 [OK] をクリックします。
[新しい ASP.NET Core Web アプリケーション] ダイアログが表示されるのを待ちます。
[.NET Core] 、 [ASP.NET Core 2.2] 、および [Web アプリケーション (モデル ビュー コントローラー)] テンプレートを選択します。
[認証] に [認証なし] が設定されていることを確認してください。
[OK] を選択します。
簡単な変更をいくつか行い、サイトのメニュー、レイアウト、home ページを決めます。
Views/Shared/_Layout.cshtml
を開き、次の変更を行います。
"ContosoUniversity" をすべて "Contoso University" に変更します。 これは 3 回出てきます。
メニュー エントリとして「About」、「Students」、「Courses」、「Instructors」、「Departments」を追加し、「Privacy」メニュー エントリを削除します。
変更が強調表示されます。
<!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" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
crossorigin="anonymous"
integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>
</environment>
<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-controller="Home" asp-action="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-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="About">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Courses" asp-action="Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Departments" asp-action="Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<partial name="_CookieConsentPartial" />
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2019 - Contoso University - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
</environment>
<environment exclude="Development">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"
asp-fallback-src="~/lib/jquery/dist/jquery.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
</script>
</environment>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>
Views/Home/Index.cshtml
で、ファイルの中身を次のコードに変更し、ASP.NET と MVC に関するテキストをこのアプリケーションに関するテキストに置き換えます。
@{
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 MVC web application.
</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.asp.net/en/latest/data/ef-mvc/intro.html">See the tutorial »</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-mvc/intro/samples/cu-final">See project source code »</a></p>
</div>
</div>
CTRL を押しながら F5 を押してプロジェクトを実行するか、メニューで [デバッグ] > [デバッグなしで開始] の順に選択します。 一連のチュートリアルで作成するページの home ページとタブが表示されます。
プロジェクトに EF Core サポートを追加するには、対象とするデータベース プロバイダーをインストールします。 このチュートリアルでは SQL Server を使用します。プロバイダー パッケージは Microsoft.EntityFrameworkCore.SqlServer です。 このパッケージは Microsoft.AspNetCore.App メタパッケージに含まれているので、パッケージを参照する必要はありません。
EF SQL Server パッケージとその依存関係 (Microsoft.EntityFrameworkCore
と Microsoft.EntityFrameworkCore.Relational
) により、EF のランタイム サポートが提供されます。 後の移行チュートリアルでツール パッケージを追加します。
Entity Framework Core で利用できるその他のデータベース プロバイダーに関しては、「データベース プロバイダー」を参照してください。
次に、Contoso University アプリケーションのエンティティ クラスを作成します。 次の 3 つのエンティティから始めます。
Student
エンティティと Enrollment
エンティティの間に一対多の関係があり、Course
エンティティと Enrollment
エンティティの間に一対多の関係があります。 言い換えると、1 人の学生をさまざまな講座に登録し、1 つの講座にたくさんの学生を登録できます。
次のセクションでは、エンティティごとにクラスを作成します。
[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
プロパティは、このクラスに相当するデータベース テーブルの主キー列になります。 既定では、Entity Framework は、ID
または classnameID
という名前のプロパティを主キーとして解釈します。
Enrollments
プロパティはナビゲーション プロパティです。 ナビゲーション プロパティには、このエンティティに関連する他のエンティティが含まれます。 この例では、Student entity
の Enrollments
プロパティで、その Student
エンティティに関連するすべての Enrollment
エンティティが保持されます。 つまり、データベースの Student
行に関連する Enrollment
行 (StudentID 外部キー列にその学生の主キー値が含まれる行) が 2 つある場合、その Student
エンティティの Enrollments
ナビゲーション プロパティには、それら 2 つの Enrollment
エンティティが含まれます。
ナビゲーション プロパティに複数のエンティティが含まれる場合 (多対多または一対多の関係で)、その型はリストにする必要があります。ICollection<T>
のように、エンティティを追加、削除、更新できるリストです。 ICollection<T>
、または List<T>
や HashSet<T>
などの型を指定することができます。 ICollection<T>
を指定した場合、EF では既定で HashSet<T>
コレクションが作成されます。
[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
エンティティに関連付けられており、1 つの Student
エンティティだけを保持できます (先に見た、複数の Enrollment
エンティティを保持できる Student.Enrollments
ナビゲーション プロパティとは異なります)。
CourseID
プロパティは外部キーです。それに対応するナビゲーション プロパティは Course
です。 Enrollment
エンティティは 1 つの Course
エンティティに関連付けられます。
Entity Framework は <navigation property name><primary key property name>
という名前が付いている場合、プロパティを外部キー プロパティとして解釈します。たとえば、Student
ナビゲーション プロパティの StudentID
です。Student
エンティティの主キーが ID
であるためです。 外部キーにも <primary key property name>
という単純な名前を付けることができます。たとえば、CourseID
です。Course
エンティティの主キーが CourseID
であるためです。
[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
属性については、このシリーズの後のチュートリアルで詳しく学習します。 基本的に、この属性によって、講座の主キーをデータベースに生成させず、自分で入力できるようになります。
所与のデータ モデルの Entity Framework 機能を調整するメイン クラスは、データベース コンテキスト クラスです。 このクラスは、Microsoft.EntityFrameworkCore.DbContext
クラスから派生させて作成します。 自分のコードでは、データ モデルに含めるエンティティを自分で指定します。 Entity Framework の特定の動作をカスタマイズすることもできます。 このプロジェクトでは、クラスに SchoolContext
という名前が付けられています。
プロジェクト フォルダーで、Data という名前のフォルダーを作成します。
[Data] フォルダーで、SchoolContext.cs
という名前の新しいクラス ファイルを作成し、テンプレート コードを次のコードに変更します。
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
}
}
このコードによって、エンティティ セットごとに DbSet
プロパティが作成されます。 Entity Framework の用語では、エンティティ セットは通常はデータベース テーブルに対応し、エンティティはテーブルの行に対応します。
DbSet<Enrollment>
ステートメントと DbSet<Course>
ステートメントは省略しても同じ動作をします。 Entity Framework にはそれらが暗黙的に含まれることがあります。Student
エンティティが Enrollment
エンティティを参照し、Enrollment
エンティティが Course
エンティティを参照するためです。
データベースが作成されると、EF によって、DbSet
プロパティと同じ名前を持つテーブルが作成されます。 一般的にコレクションのプロパティ名は複数形 (Student ではなく、Students) ですが、テーブル名を複数にするかどうかについては、開発者の間で意見が分かれています。 このチュートリアル シリーズでは、DbContext に単数のテーブル名を指定して既定の動作をオーバーライドします。 そのために、最後の DbSet プロパティの後に、次の強調表示されているコードを追加します。
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}
コンパイラ エラーのチェックとしてプロジェクトをビルドします。
ASP.NET Core は既定で依存関係の挿入を実装します。 サービス (EF データベース コンテキストなど) は、アプリケーションの起動時に依存関係の挿入に登録されます。 これらのサービス (MVC コント ローラーなど) を必要とするコンポーネントには、コンストラクターのパラメーターを介してこれらのサービスが指定されます。 このチュートリアルの後半で、コンテキスト インスタンスを取得するコントローラー コンストラクター コードが登場します。
SchoolContext
をサービスとして登録するには、Startup.cs
を開き、強調表示されている行を ConfigureServices
メソッドに追加します。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
}
DbContextOptionsBuilder
オブジェクトでメソッドが呼び出され、接続文字列の名前がコンテキストに渡されます。 ローカル開発の場合、ASP.NET Core 構成システムによって appsettings.json
ファイルから接続文字列が読み取られます。
名前空間の ContosoUniversity.Data
と Microsoft.EntityFrameworkCore
に対して using
ステートメントを追加し、プロジェクトをビルドします。
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Http;
appsettings.json
ファイルを開き、次の例で示されているように接続文字列を追加します。
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
この接続文字列によって SQL Server LocalDB データベースが指定されます。 LocalDB は SQL Server Express データベース エンジンの軽量版であり、実稼働ではなく、アプリケーションの開発を意図して設計されています。 LocalDB は要求時に開始され、ユーザー モードで実行されるため、複雑な構成はありません。 既定では、LocalDB は C:/Users/<user>
ディレクトリに .mdf データベース ファイルを作成します。
Entity Framework によって空のデータベースが自動的に作成されます。 このセクションでは、テスト データを入力する目的で、データベースの作成後に呼び出されるメソッドを記述します。
ここでは、データベースを自動的に作成する EnsureCreated
メソッドを使用します。 後のチュートリアルでは、モデル変更の処理方法について学習します。データベースを削除し、再作成するのではなく、Code First Migrations を利用してデータベース スキーマを変更します。
[データ] フォルダーで DbInitializer.cs
という名前の新しいクラス ファイルを作成し、テンプレート コードを次のコードに変更します。このコードにより、必要なときにデータベースが作成され、新しいデータベースにテスト データが読み込まれます。
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("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.Students.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.Courses.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.Enrollments.Add(e);
}
context.SaveChanges();
}
}
}
このコードはデータベースに学生が存在するかどうかを確認し、存在しない場合、そのデータベースは新しく、テスト データを入力する必要があると見なします。 List<T>
コレクションではなく配列にテスト データを読み込み、パフォーマンスを最適化します。
Program.cs
で、アプリケーションの起動時に次を実行するように Main
メソッドを変更します。
- 依存関係挿入コンテナーからデータベース コンテキスト インスタンスを取得します。
- seed メソッドを呼び出し、コンテキストを渡します。
- seed メソッドが完了したら、コンテキストを破棄します。
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>();
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>();
});
}
}
アプリケーションを初めて実行すると、データベースが作成され、テスト データが設定されます。 データ モデルを変更するたび:
- データベースを削除します。
- シード メソッドを更新し、同じようにして新しいデータベースで新たに開始します。
後のチュートリアルでは、データ モデルが変わったとき、データベースを削除して作り直すのではなく、修正する方法について学習します。
このセクションでは、Visual Studio のスキャフォールディング エンジンを使用して、MVC のコントローラーとビューを追加します。それらにより、EF を使用して、クエリが実行され、データが保存されます。
CRUD アクションのメソッドとビューの自動作成は、スキャフォールディングと言います。 一般的には生成されたコードは修正しないのに対し、スキャフォールディングされたコードを開始点として独自の要件に合うように変更できるという点で、スキャフォールディングはコード生成と異なります。 生成されたコードをカスタマイズする必要があるとき、部分クラスを利用するか、状況が変わったときにコードを再生成します。
- ソリューション エクスプローラーの Controllers フォルダーを右クリックし、[追加] > [スキャフォールディングされた新しい項目] の順に選択します。
- [スキャフォールディングを追加] ダイアログ ボックスで:
- [Entity Framework を使用したビューがある MVC コントローラー] を選択します。
- [追加] をクリックします。 [Entity Framework を使用してビューがある MVC コントローラーを追加する] ダイアログ ボックスが表示されます。
- [モデル クラス] で [Student] を選択します。
- [データ コンテキスト クラス] で [SchoolContext] を選択します。
- 名前は StudentsController をそのまま選択します。
- [追加] をクリックします。
Visual Studio スキャフォールディング エンジンにより、StudentsController.cs
ファイルと、コントローラーで動作するビューのセット (.cshtml
ファイル) が作成されます。
コントローラーによりコンストラクター パラメーターとして SchoolContext
が受け取られることに注意してください。
namespace ContosoUniversity.Controllers
{
public class StudentsController : Controller
{
private readonly SchoolContext _context;
public StudentsController(SchoolContext context)
{
_context = context;
}
ASP.NET Core 依存関係挿入では、SchoolContext
のインスタンスがコントローラーに渡されます。 これは、Startup.cs
ファイルで構成しました。
コントローラーには Index
アクション メソッドが含まれます。これはデータベースにあるすべての学生を表示します。 このメソッドはデータベース コンテキスト インスタンスの Students
プロパティを読み取り、Students エンティティ セットから学生の一覧を取得します。
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}
このコードの非同期プログラミング要素については、チュートリアルで後ほど学習します。
Views/Students/Index.cshtml
ビューには、この一覧で表形式で表示されます。
@model IEnumerable<ContosoUniversity.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
CTRL を押しながら F5 を押してプロジェクトを実行するか、メニューで [デバッグ] > [デバッグなしで開始] の順に選択します。
[Students] タブをクリックすると、DbInitializer.Initialize
メソッドによって挿入されたテスト データが表示されます。 ブラウザーのウィンドウ幅によって決まることですが、Students
タブ リンクはページの一番上に表示されるか、右上隅のナビゲーション アイコンをクリックしないと表示されません。
アプリケーションを起動する葉、DbInitializer.Initialize
メソッドが EnsureCreated
を呼び出します。 EF はデータベースがないことを認識し、作成します。Initialize
メソッド コードの残りの部分により、データベースにデータが入力されます。 SQL Server Object Explorer (SSOX) を利用し、Visual Studio でデータベースを表示できます。
ブラウザーを閉じます。
SSOX ウィンドウがまだ開いていない場合、Visual Studio の [表示] メニューから選択します。
SSOX で (localdb)\MSSQLLocalDB > [データベース] をクリックし、appsettings.json
ファイルの接続文字列にあるデータベース名のエントリをクリックします。
[テーブル] ノードを展開し、データベースのテーブルを表示します。
[Student] テーブルを右クリックし、 [データの表示] をクリックすると、作成された列とテーブルに挿入された行が表示されます。
データベース ファイルの .mdf と .ldf は C:\Users<ユーザー名> フォルダーにあります。
アプリの起動時に実行される初期化子メソッドで EnsureCreated
を呼び出すため、Student
クラスを変更し、データベースを削除し、アプリケーションを再実行できます。変更に合わせてデータベースが自動的に再作成されます。 たとえば、Student
クラスに EmailAddress
プロパティを追加する場合、再作成されたテーブルに新しい EmailAddress
列が表示されます。
規約を利用することや Entity Framework が想定を行うことにより、Entity Framework が完全なデータベースを自動作成するために記述しなければならないコードの量が最小限に抑えられます。
DbSet
プロパティの名前がテーブル名として使用されます。DbSet
プロパティによって参照されないエンティティについては、エンティティ クラス名がテーブル名として使用されます。- 列名には、エンティティ プロパティ名が使用されます。
- ID または classnameID という名前が付けられているエンティティ プロパティは主キーのプロパティとして認識されます。
- <ナビゲーション プロパティ名><主キー プロパティ名> という名前 (たとえば、
Student
ナビゲーション プロパティの場合、Student
エンティティの主キーがID
なので、StudentID
となります) が付いている場合、プロパティは外部キー プロパティとして解釈されます。 外部キー プロパティにも <主キー プロパティ名> の単純な名前を付けることができます (たとえば、Enrollment
エンティティの主キーはEnrollmentID
なのでEnrollmentID
)。
従来の動作をオーバーライドできます。 たとえば、このチュートリアルで先に見たように、テーブル名を明示的に指定できます。 また、列名を設定し、任意のプロパティを主キーまたは外部キーとして設定できます。これについては、このシリーズの後のチュートリアルで学習します。
ASP.NET Core と EF Core では、非同期プログラミングが既定のモードです。
Web サーバーでは、利用できるスレッド数に限りがあります。負荷が高い状況では、利用できるスレッドが全部使われる可能性があります。 その場合、スレッドが解放されるまでサーバーは新しい要求を処理できません。 同期コードの場合、たくさんのスレッドが関連付けられていても、I/O の完了を待っているため、実際には何の作業も行っていないということがあります。 非同期コードの場合、あるプロセスが I/O の完了を待っているとき、そのスレッドは解放され、サーバーによって他の要求の処理に利用できます。 結果として、非同期コードの場合、サーバー リソースをより効率的に利用できます。サーバーは、より多くのトラフィックを遅延なく処理できます。
非同期コードは実行時に少量のオーバーヘッドを発生させるが、トラフィックが少ない場合、パフォーマンスに与える影響は無視して構わない程度です。トラフィックが多い場合、相当なパフォーマンス改善が見込まれます。
次のコードでは、キーワード async
、戻り値 Task<T>
、キーワード await
、メソッド ToListAsync
によりコードの実行が非同期になります。
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}
- キーワード
async
は、メソッド本文の一部にコールバックを生成し、返されたTask<IActionResult>
オブジェクトを自動作成するようにコンパイラに伝えます。 - 戻り値の型
Task<IActionResult>
は、進行中の作業と型IActionResult
の結果を表します。 - キーワード
await
により、コンパイラはメソッドを 2 つに分割します。 最初の部分は、非同期で開始される操作で終わります。 2 つ目の部分は、操作の完了時に呼び出されるコールバック メソッドに入ります。 ToListAsync
は、ToList
拡張メソッドの非同期バージョンです。
Entity Framework を利用する非同期コードの記述で注意すべき点:
- クエリやコマンドをデータベースに送信するステートメントのみが非同期で実行されます。 たとえば、
ToListAsync
、SingleOrDefaultAsync
、SaveChangesAsync
などです。var students = context.Students.Where(s => s.LastName == "Davolio")
など、IQueryable
を変更するだけのステートメントは含まれません。 - EF コンテキストはスレッド セーフではありません。複数の操作を並列実行しないでください。 非同期 EF メソッドを呼び出すとき、
await
キーワードを常に使用します。 - 非同期コードのパフォーマンス上の利点を最大限に活用する場合、(ページングなどのために) ライブラリ パッケージを利用しているのであれば、それがクエリをデータベースに送信させる Entity Framework メソッドを呼び出す場合、非同期を利用する必要があります。
.NET の非同期プログラミングについては、「非同期の概要」を参照してください。
基本的な CRUD (作成、読み取り、更新、削除) 操作の実行方法を学習するには、次のチュートリアルに進んでください。
ASP.NET Core に関するフィードバック
ASP.NET Core はオープンソース プロジェクトです。 フィードバックを提供するにはリンクを選択します。
その他のリソース
トレーニング
モジュール
最小 API、Entity Framework Core、および ASP.NET Core でデータベースを使用する - Training
最小 API アプリケーションにデータベースを追加する方法について説明します。