パート 4、ASP.NET Core MVC アプリにモデルを追加する

作成者: Rick Anderson および Jon P Smith

このセクションでは、データベースで映画を管理するクラスが追加されます。 これらのクラスは、M VC アプリの " デル" 部分です。

これらのモデル クラスは、データベースを操作するために Entity Framework Core (EF Core) で使用されます。 EF Core は、記述する必要があるデータ アクセス コードを簡略化するオブジェクト リレーショナル マッピング (ORM) フレームワークです。

作成されたモデル クラスは、_*P**lain O ld C LR O bjects の頭文字を取って *POCO _ クラスと呼ばれます。 POCO クラスには EF Core に対する依存関係はありません。 これらは、データベースに格納されるデータのプロパティのみを定義します。

このチュートリアルでは、まずモデル クラスが作成され、EF コアによってデータベースが作成されます。

データ モデル クラスの追加

Models フォルダーを右クリックし、 [追加] > [クラス] の順に選択します。 ファイルに Movie.cs という名前を付けます。

次のコードで Models/Movie.cs ファイルを更新します。

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int Id { get; set; }
        public string Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
}

Movie クラスには、データベースで主キー用に必要となる Id フィールドが含まれています。

ReleaseDateDataType 属性により、データの型 (Date) が指定されます。 この属性を使用する場合:

  • ユーザーは日付フィールドに時刻の情報を入力する必要はありません。
  • 日付のみが表示され、時刻の情報は表示されません。

DataAnnotations は、後のチュートリアルで説明されます。

NuGet パッケージを追加する

[ツール] メニューで、 [NuGet パッケージ マネージャー] > [パッケージ マネージャー コンソール] (PMC) の順に選択します。

PMC メニュー

PMC で次のコマンドを実行します。

Install-Package Microsoft.EntityFrameworkCore.Design

上記のコマンドにより次が追加されます。

  • EF Core SQL Server プロバイダー。 プロバイダー パッケージによって、依存関係として EF Core パッケージがインストールされます。
  • パッケージによって使用されるユーティリティは、このチュートリアルの後半で、スキャフォールディングの手順で自動的にインストールされます。

コンパイラ エラーのチェックとしてプロジェクトをビルドします。

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

スキャフォールディング ツールを使用し、ムービー モデルの CreateReadUpdateDelete の (CRUD) のページを生成します。

ソリューション エクスプローラーControllers フォルダーを右クリックし、 [追加]、[スキャフォールディングされた新しい項目] の順に選択します。

前述の手順を参照

[スキャフォールディングを追加] ダイアログで、 [Entity Framework を使用したビューがある MVC コントローラー]、[追加] の順に選択します。

[スキャフォールディングを追加] ダイアログ

[Entity Framework を使用したビューがある MVC コントローラーを追加する] ダイアログを完了します。

  • [モデル クラス] ドロップ ダウンで、 [Movie (MvcMovie.Models)] を選択します。
  • Data context class 行で、 + (+) 記号を選択します。
    • [データ コンテキストの追加] ダイアログで、クラス名 MvcMovie.Data.MvcMovieContext が生成されます。
    • [追加] を選択します。
  • ビューコントローラー名: 既定値のままにします。
  • [追加] を選択します。

[データの追加] コンテキストで既定値のままにします

スキャフォールディングによって次が更新されます。

  • 必要なパッケージ参照を MvcMovie.csproj プロジェクト ファイルに挿入します。
  • Startup.cs ファイルの Startup.ConfigureServices でデータベース コンテキストを登録します。
  • データベース接続文字列を appsettings.json ファイルに追加します。

スキャフォールディングによって次が作成されます。

  • ムービー コントローラー: Controllers/MoviesController.cs
  • 作成削除詳細編集、および インデックス ページ用の Razor ビュー ファイル: Views/Movies/*.cshtml
  • データベース コンテキスト クラス: Data/MvcMovieContext.cs

これらのファイルとファイル更新の自動作成は、スキャフォールディング と呼ばれます。

データベースが存在しないため、スキャフォールディング ページをまだ使用できません。 アプリを実行し、 [Movie App] リンクを選択すると、 [データベースを開けません] または [そのようなテーブルはありません: Movie] エラー メッセージが表示されます。

最初の移行

EF Core 移行機能を使用し、データベースを作成します。 移行は、データ モデルに合わせてデータベースを作成および更新する一連のツールです。

[ツール] メニューで、 [NuGet パッケージ マネージャー] > [パッケージ マネージャー コンソール] の順に選択します。

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

Add-Migration InitialCreate
Update-Database

  • Add-Migration InitialCreate:Migrations/{timestamp}_InitialCreate.cs 移行ファイルが生成されます。 InitialCreate 引数は、移行の名前です。 任意の名前を使用できますが、慣例により、移行を説明する名前が選択されます。 これは最初の移行であるため、生成されたクラスには、データベース スキーマを作成するコードが含まれています。 データベース スキーマは、MvcMovieContext クラスで指定されたモデルに基づきます。

  • Update-Database:前のコマンドで作成された最新の移行にデータベースを更新します。 このコマンドにより Migrations/{time-stamp}_InitialCreate.cs ファイルで Up メソッドが実行され、データベースが作成されます。

Update-Database コマンドでは、以下の警告が生成されます。

エンティティ型 'Movie' の decimal 列 'Price' に型が指定されていません。 これにより、値が既定の有効桁数と小数点以下桁数に収まらない場合、自動的に切り捨てられます。 'HasColumnType()' を使用してすべての値に適合する SQL server 列の型を明示的に指定します。

前の警告は無視してください。これは、後のチュートリアルで修正されます。

EF Core 用の PMC ツールの詳細については、EF Core ツール リファレンスの Visual Studio の PMC に関するページを参照してください。

アプリをテストする

アプリを実行し、 [Movie App] リンクを選択します。

次のような例外が表示された場合は、移行手順を実行していなかった可能性があります。

SqlException: Cannot open database "MvcMovieContext-1" requested by the login. The login failed.

注意

Price フィールドに小数点のコンマを入力できない場合があります。 小数点にコンマ (",") を使う英語以外のロケール、および英語 (米国) 以外の日付形式で、jQuery 検証をサポートするには、アプリをグローバル化する必要があります。 グローバル化の手順については、この GitHub の記事をご覧ください。

生成されたデータベース コンテキスト クラスと登録を調べる

EF Core では、データ アクセスはモデルを利用して実行されます。 モデルはエンティティ クラスと、データベースとのセッションを表すコンテキスト オブジェクトから構成されます。 このコンテキスト オブジェクトにより、データのクエリと保存が可能になります。 データベース コンテキストは Microsoft.EntityFrameworkCore.DbContext から派生し、データ モデルに含めるエンティティを指定します。

スキャフォールディングにより、Data/MvcMovieContext.cs データベース コンテキスト クラスが作成されます。

using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;

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

        public DbSet<Movie> Movie { get; set; }
    }
}

上記のコードによって、データベース内のムービーを表す DbSet <Movie> プロパティが作成されます。

ASP.NET Core には、依存関係挿入 (DI) が組み込まれています。 データベース コンテキストなどのサービスは、Startup の DI に登録する必要があります。 これらのサービスを必要とするコンポーネントには、コンストラクターのパラメーターを介してこれらのサービスが指定されます。

Controllers/MoviesController.cs ファイル内のコンストラクターでは、依存性の挿入を使って MvcMovieContext データベース コンテキストがコントローラーに挿入されています。 データベース コンテキストは、コントローラーの各 CRUD メソッドで使用されます。

スキャフォールディングによって、Startup.ConfigureServices で次の強調表示されたコードが生成されました。

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

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

ASP.NET Core 構成システムによって "MvcMovieContext" データベース接続文字列が読み取られます。

生成されたデータベース接続文字列を調べる

スキャフォールディングによって、接続文字列が appsettings.json ファイルに追加されました。

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

ローカル開発の場合、ASP.NET Core 構成システムによって appsettings.json ファイルから ConnectionString キーが読み取られます。

InitialCreate クラス

Migrations/{timestamp}_InitialCreate.cs 移行ファイルを調べます。

public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Movie",
            columns: table => new
            {
                Id = table.Column<int>(type: "int", nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                Title = table.Column<string>(type: "nvarchar(max)", nullable: true),
                ReleaseDate = table.Column<DateTime>(type: "datetime2", nullable: false),
                Genre = table.Column<string>(type: "nvarchar(max)", nullable: true),
                Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Movie", x => x.Id);
            });
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "Movie");
    }
}

上のコードでは以下の操作が行われます。

  • InitialCreate.Up により Movie テーブルが作成され、主キーとして Id が構成されます。
  • InitialCreate.Down により、Up 移行で行われたスキーマ変更が元に戻ります。

コントローラーの依存関係挿入

Controllers/MoviesController.cs ファイルを開いて、コンストラクターを調べます。

public class MoviesController : Controller
{
    private readonly MvcMovieContext _context;

    public MoviesController(MvcMovieContext context)
    {
        _context = context;
    }

コンストラクターでは、依存性の注入を使ってデータベース コンテキスト (MvcMovieContext) がコントローラーに挿入されています。 データベース コンテキストは、コントローラーの各 CRUD メソッドで使用されます。

Create ページをテストします。 データを入力して送信します。

[編集][詳細][削除] の各ページをテストします。

厳密に型指定されたモデルと @model ディレクティブ

コントローラーで ViewData ディクショナリを使ってビューにデータまたはオブジェクトを渡す方法を前に示しました。 ViewData ディクショナリは動的オブジェクトであり、ビューに情報を渡すための便利な遅延バインディングの方法を提供します。

MVC には、厳密に型指定されたモデル オブジェクトをビューに渡す機能があります。 この厳密に型指定された方法では、コンパイル時にコードを確認できます。 スキャフォールディング メカニズムでは、 MoviesController クラスとビューで、型指定されたモデルを渡しました。

Controllers/MoviesController.cs ファイルで生成された Details メソッドを調べてください。

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

    var movie = await _context.Movie
        .FirstOrDefaultAsync(m => m.Id == id);
    if (movie == null)
    {
        return NotFound();
    }

    return View(movie);
}

通常、id パラメーターはルート データとして渡されます。 たとえば、https://localhost:5001/movies/details/1 は次のように設定します。

  • コントローラーを movies コントローラーに (最初の URL セグメント)。
  • アクションを details に (2 番目の URL セグメント)。
  • id を 1 に (最後の URL セグメント)。

id は、次の例で示すようにクエリ文字列で渡すことができます。

https://localhost:5001/movies/details?id=1

id 値が指定されていない場合、id パラメーターは null 許容型 (int?) として定義されます。

ルート データまたはクエリ文字列の値と一致するムービー エンティティを選択するため、ラムダ式FirstOrDefaultAsync メソッドに渡されます。

var movie = await _context.Movie
    .FirstOrDefaultAsync(m => m.Id == id);

ムービーが見つかった場合、Movie モデルのインスタンスが Details ビューに渡されます。

return View(movie);

Views/Movies/Details.cshtml ファイルの内容を確認してください。

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Movie</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.ReleaseDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.ReleaseDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Genre)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Genre)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Price)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Price)
        </dd>
    </dl>
</div>
<div>
    <a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
    <a asp-action="Index">Back to List</a>
</div>

ビュー ファイルの一番上にある @model ステートメントにより、ビューで求められるオブジェクトの型が指定されます。 ムービー コントローラーが作成されたとき、次の @model ステートメントが含まれました。

@model MvcMovie.Models.Movie

この @model ディレクティブにより、コントローラーでビューに渡されたムービーにアクセスできます。 Model オブジェクトは厳密に型指定されます。 たとえば、Details.cshtml ビューでは、コードで厳密に型指定された Model オブジェクトを使って、DisplayNameFor および DisplayFor HTML ヘルパーに各ムービー フィールドを渡しています。 Create および Edit のメソッドとビューも、Movie モデル オブジェクトを渡します。

Movies コントローラーの Index.cshtml ビューと Index メソッドを確認してください。 コードで View メソッドを呼び出すときの List オブジェクトの作成方法に注意してください。 コードでは、この Movies リストを Index アクション メソッドからビューに渡しています。

// GET: Movies
public async Task<IActionResult> Index()
{
    return View(await _context.Movie.ToListAsync());
}

ムービー コントローラーが作成されたとき、スキャフォールディングにより、Index.cshtml ファイルの一番上に次の @model ステートメントが含まれました。

@model IEnumerable<MvcMovie.Models.Movie>

@model ディレクティブにより、厳密に型指定された Model オブジェクトを使って、コントローラーがビューに渡したムービーのリストにアクセスできます。 たとえば、Index.cshtml ビューのコードでは、foreach ステートメントを使って厳密に型指定された Model オブジェクトのムービーをループ処理しています。

@model IEnumerable<MvcMovie.Models.Movie>

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </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>

Model オブジェクトは IEnumerable<Movie> オブジェクトとして厳密に型指定されているので、ループ内の各項目は Movie として型指定されます。 それ以外の利点として、コンパイラはコードで使用されている型を検証します。

Entity Framework Core の SQL ログ

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

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

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

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

その他の技術情報

データ モデル クラスの追加

Models フォルダーを右クリックし、 [追加] > [クラス] の順に選択します。 ファイルに Movie.cs という名前を付けます。

次のコードで Movie.cs ファイルを更新します。

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int Id { get; set; }
        public string Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
}

Movie クラスには、データベースで主キー用に必要となる Id フィールドが含まれています。

ReleaseDateDataType 属性により、データの型 (Date) が指定されます。 この属性を使用する場合:

  • ユーザーは日付フィールドに時刻の情報を入力する必要はありません。
  • 日付のみが表示され、時刻の情報は表示されません。

DataAnnotations は、後のチュートリアルで説明されます。

NuGet パッケージを追加する

[ツール] メニューで、 [NuGet パッケージ マネージャー] > [パッケージ マネージャー コンソール] (PMC) の順に選択します。

PMC メニュー

PMC で次のコマンドを実行します。

Install-Package Microsoft.EntityFrameworkCore.SqlServer

上記のコマンドによって EF Core SQL Server プロバイダーが追加されます。 プロバイダー パッケージによって、依存関係として EF Core パッケージがインストールされます。 追加のパッケージは、このチュートリアルの後半で、スキャフォールディングの手順で自動的にインストールされます。

データベース コンテキスト クラスを作成する

データベース コンテキスト クラスは、Movie モデルの EF Core 機能 (作成、読み取り、更新、削除) を調整するために必要です。 データベース コンテキストは Microsoft.EntityFrameworkCore.DbContext から派生し、データ モデルに含めるエンティティを指定します。

Data フォルダーを作成します。

次のコードで Data/MvcMovieContext.cs ファイルを追加します。

using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;

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

        public DbSet<Movie> Movie { get; set; }
    }
}

上記のコードによって、エンティティ セットの DbSet<Movie> プロパティが作成されます。 Entity Framework の用語では、エンティティ セットは通常はデータベース テーブルに対応します。 エンティティはテーブル内の行に対応します。

データベース コンテキストの登録

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

Startup.cs の先頭に次の using ステートメントを追加します。

using MvcMovie.Data;
using Microsoft.EntityFrameworkCore;

Startup.ConfigureServices で次の強調表示されたコードを追加します。

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

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

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

データベース接続文字列を調べる

接続文字列を appsettings.json ファイルに追加します。

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

コンパイラ エラーのチェックとしてプロジェクトをビルドします。

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

スキャフォールディング ツールを使用し、ムービー モデルの作成、読み取り、更新、削除の (CRUD) ページを生成します。

ソリューション エクスプローラー で、Controllers フォルダーを右クリックし、 [追加]、[スキャフォールディングされた新しい項目] の順に選択します。

前述の手順を参照

[スキャフォールディングを追加] ダイアログで、 [Entity Framework を使用したビューがある MVC コントローラー]、[追加] の順に選択します。

[スキャフォールディングを追加] ダイアログ

[コントローラーの追加] ダイアログ ボックスを完了します。

  • モデル クラス: Movie (MvcMovie.Models)
  • データ コンテキスト クラス: MvcMovieContext (MvcMovie.Data)

[データの追加] コンテキスト

  • ビュー: 各オプションの既定値をオンにします。
  • コントローラー名: 既定の MoviesController のままにします。
  • [追加] を選択します。

Visual Studio では、次が作成されます。

  • ムービー コントローラー (Controllers/MoviesController.cs)
  • 作成、削除、詳細、編集、およびインデックス ページ用の Razor ビュー ファイル (Views/Movies/*.cshtml)

このようなファイルの自動作成は、"スキャフォールディング" と呼ばれます。

データベースが存在しないため、スキャフォールディング ページをまだ使用できません。 アプリを実行し、 [Movie App] リンクをクリックすると、 [データベースを開けません] または [そのようなテーブルはありません:Movie] というエラー メッセージが表示されます。

最初の移行

EF Core 移行機能を使用し、データベースを作成します。 移行は、データ モデルに合わせてデータベースを作成したり、更新したりできる一連のツールです。

[ツール] メニューで、 [NuGet パッケージ マネージャー] > [パッケージ マネージャー コンソール] (PMC) の順に選択します。

PMC で、次のコマンドを入力します。

Add-Migration InitialCreate
Update-Database
  • Add-Migration InitialCreate:Migrations/{timestamp}_InitialCreate.cs 移行ファイルが生成されます。 InitialCreate 引数は、移行の名前です。 任意の名前を使用できますが、慣例により、移行を説明する名前が選択されます。 これは最初の移行であるため、生成されたクラスには、データベース スキーマを作成するコードが含まれています。 データベース スキーマは、MvcMovieContext クラスで指定されたモデルに基づきます。

  • Update-Database:前のコマンドで作成された最新の移行にデータベースを更新します。 このコマンドにより Migrations/{time-stamp}_InitialCreate.cs ファイルで Up メソッドが実行され、データベースが作成されます。

    データベース更新コマンドにより、次の警告が生成されます。

    エンティティ型 'Movie' の decimal 列 'Price' に型が指定されていません。 これにより、値が既定の有効桁数と小数点以下桁数に収まらない場合、自動的に切り捨てられます。 'HasColumnType()' を使用してすべての値に適合する SQL server 列の型を明示的に指定します。

    この警告は無視して構いません。後のチュートリアルで修正されます。

EF Core 用の PMC ツールの詳細については、EF Core ツール リファレンスの Visual Studio の PMC に関するページを参照してください。

InitialCreate クラス

Migrations/{timestamp}_InitialCreate.cs 移行ファイルを調べます。

public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Movie",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:ValueGenerationStrategy", 
                                 SqlServerValueGenerationStrategy.IdentityColumn),
                Title = table.Column<string>(nullable: true),
                ReleaseDate = table.Column<DateTime>(nullable: false),
                Genre = table.Column<string>(nullable: true),
                Price = table.Column<decimal>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Movie", x => x.Id);
            });
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "Movie");
    }
}

Up メソッドにより Movie テーブルが作成され、主キーとして Id が構成されます。 Down メソッドにより、Up 移行で行われたスキーマ変更が元に戻ります。

アプリのテスト

  • アプリを実行し、 [Movie App] リンクをクリックします。

    次のような例外が表示された場合:

SqlException: Cannot open database "MvcMovieContext-1" requested by the login. The login failed.

移行手順を実行しなかった可能性があります。

  • Create ページをテストします。 データを入力して送信します。

    注意

    Price フィールドに小数点のコンマを入力できない場合があります。 小数点にコンマ (",") を使う英語以外のロケール、および英語 (米国) 以外の日付形式で、jQuery 検証をサポートするには、アプリをグローバル化する必要があります。 グローバル化の手順については、この GitHub の記事をご覧ください。

  • [編集][詳細][削除] の各ページをテストします。

コントローラーの依存関係挿入

Controllers/MoviesController.cs ファイルを開いて、コンストラクターを調べます。

public class MoviesController : Controller
{
    private readonly MvcMovieContext _context;

    public MoviesController(MvcMovieContext context)
    {
        _context = context;
    }

コンストラクターでは、依存性の注入を使ってデータベース コンテキスト (MvcMovieContext) がコントローラーに挿入されています。 データベース コンテキストは、コントローラーの各 CRUD メソッドで使用されます。

厳密に型指定されたモデルと @model キーワード

コントローラーで ViewData ディクショナリを使ってビューにデータまたはオブジェクトを渡す方法を前に示しました。 ViewData ディクショナリは動的オブジェクトであり、ビューに情報を渡すための便利な遅延バインディングの方法を提供します。

MVC にも、厳密に型指定されたモデル オブジェクトをビューに渡す機能があります。 この厳密に型指定された方法では、コンパイル時にコードを確認できます。 スキャフォールディング メカニズムでは、MoviesController のクラスとビューで、この手法 (つまり、厳密に型指定されたモデルを渡しました) が使用されました。

Controllers/MoviesController.cs ファイルで生成された Details メソッドを調べてください。

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

    var movie = await _context.Movie
        .FirstOrDefaultAsync(m => m.Id == id);
    if (movie == null)
    {
        return NotFound();
    }

    return View(movie);
}

通常、id パラメーターはルート データとして渡されます。 たとえば、https://localhost:5001/movies/details/1 は次のように設定します。

  • コントローラーを movies コントローラーに (最初の URL セグメント)。
  • アクションを details に (2 番目の URL セグメント)。
  • ID を 1 に (最後の URL セグメント)。

次のようにクエリ文字列で id を渡すこともできます。

https://localhost:5001/movies/details?id=1

ID 値が指定されない場合、id パラメーターは null 許容型 (int?) として定義されます。

ルート データまたはクエリ文字列の値と一致するムービー エンティティを選択するため、ラムダ式FirstOrDefaultAsync に渡されます。

var movie = await _context.Movie
    .FirstOrDefaultAsync(m => m.Id == id);

ムービーが見つかった場合、Movie モデルのインスタンスが Details ビューに渡されます。

return View(movie);

Views/Movies/Details.cshtml ファイルの内容を確認してください。

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Movie</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.ReleaseDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.ReleaseDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Genre)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Genre)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Price)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Price)
        </dd>
    </dl>
</div>
<div>
    <a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
    <a asp-action="Index">Back to List</a>
</div>

ビュー ファイルの一番上にある @model ステートメントにより、ビューで求められるオブジェクトの型が指定されます。 ムービー コントローラーが作成されたとき、次の @model ステートメントが含まれました。

@model MvcMovie.Models.Movie

この @model ディレクティブにより、コントローラーでビューに渡されたムービーにアクセスできます。 Model オブジェクトは厳密に型指定されます。 たとえば、Details.cshtml ビューでは、コードで厳密に型指定された Model オブジェクトを使って、DisplayNameFor および DisplayFor HTML ヘルパーに各ムービー フィールドを渡しています。 Create および Edit のメソッドとビューも、Movie モデル オブジェクトを渡します。

Movies コントローラーの Index.cshtml ビューと Index メソッドを確認してください。 コードで View メソッドを呼び出すときの List オブジェクトの作成方法に注意してください。 コードでは、この Movies リストを Index アクション メソッドからビューに渡しています。

// GET: Movies
public async Task<IActionResult> Index()
{
    return View(await _context.Movie.ToListAsync());
}

ムービー コントローラーが作成されたとき、スキャフォールディングにより、Index.cshtml ファイルの一番上に次の @model ステートメントが含まれました。

@model IEnumerable<MvcMovie.Models.Movie>

@model ディレクティブにより、厳密に型指定された Model オブジェクトを使って、コントローラーがビューに渡したムービーのリストにアクセスできます。 たとえば、Index.cshtml ビューのコードでは、foreach ステートメントを使って厳密に型指定された Model オブジェクトのムービーをループ処理しています。

@model IEnumerable<MvcMovie.Models.Movie>

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </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>

Model オブジェクトは厳密に型指定されているので (IEnumerable<Movie> オブジェクトとして)、ループ内の各項目は Movie として型指定されます。 これには、コンパイル時にコードを確認できるなどの利点があります。

その他の技術情報

データ モデル クラスの追加

Models フォルダーを右クリックし、 [追加] > [クラス] の順に選択します。 クラスに Movie と名前を付けます。

Movie クラスに次のプロパティを追加します。

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int Id { get; set; }
        public string Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
}

Movie クラスには次が含まれます。

  • データベースで主キー用に必要となる Id フィールド。

  • [DataType(DataType.Date)]: DataType 属性では、データの型 (Date) を指定します。 この属性を使用する場合:

    • ユーザーは日付フィールドに時刻の情報を入力する必要はありません。
    • 日付のみが表示され、時刻の情報は表示されません。

DataAnnotations は、後のチュートリアルで説明されます。

ムービー モデルのスキャフォールディング

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

ソリューション エクスプローラー で、Controllers フォルダーを右クリックし、 [追加]、[スキャフォールディングされた新しい項目] の順に選択します。

前述の手順を参照

[スキャフォールディングを追加] ダイアログで、 [Entity Framework を使用したビューがある MVC コントローラー]、[追加] の順に選択します。

[スキャフォールディングを追加] ダイアログ

[コントローラーの追加] ダイアログ ボックスを完了します。

  • モデル クラス: Movie (MvcMovie.Models)
  • データ コンテキスト クラス: + アイコンを選択して、既定の MvcMovie.Models.MvcMovieContext を追加します。

[データの追加] コンテキスト

  • ビュー: 各オプションの既定値をオンにします。
  • コントローラー名: 既定の MoviesController のままにします。
  • [追加] を選択します。

[コントローラーの追加] ダイアログ

Visual Studio では、次が作成されます。

  • Entity Framework Core の データベース コンテキスト クラス(Data/MvcMovieContext.cs)
  • ムービー コントローラー (Controllers/MoviesController.cs)
  • 作成、削除、詳細、編集、およびインデックス ページ用の Razor ビュー ファイル (Views/Movies/*.cshtml)

データベース コンテキストと CRUD (作成、読み取り、更新、および削除) アクション メソッドとビューの自動作成は、スキャフォールディング と言います。

アプリを実行し、 [MVC Movie](MVC ムービー) リンクをクリックすると、次のようなエラーが表示されます。

An unhandled exception occurred while processing the request.

SqlException: Cannot open database "MvcMovieContext-<GUID removed>" requested by the login. The login failed.
Login failed for user 'Rick'.

System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString

データベースを作成する必要があり、それには EF Core 移行機能を使用します。 移行では、データ モデルに一致するデータベースを作成し、データ モデルの変更時にデータベース スキーマを更新することができます。

最初の移行

このセクションでは、次のタスクを完了しました。

  • 初期移行を追加します。
  • 初期移行でデータベースを更新します。
  1. [ツール] メニューで、 [NuGet パッケージ マネージャー] > [パッケージ マネージャー コンソール] (PMC) の順に選択します。

    PMC メニュー

  2. PMC で、次のコマンドを入力します。

    Add-Migration Initial
    Update-Database
    

    Add-Migration コマンドによって最初のデータベース スキーマを作成するコードが生成されます。

    データベース スキーマは、MvcMovieContext クラスで指定されたモデルに基づきます。 Initial 引数は、移行の名前です。 任意の名前を使用できますが、慣例により、移行を説明する名前が使用されます。 詳細については、「チュートリアル パート 5 - Contoso University のサンプルに移行を適用する」を参照してください。

    Update-Database コマンドは、データベースを作成する、Migrations/{time-stamp}_InitialCreate.cs ファイルの Up メソッドを実行します。

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

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

スキャフォールディング ツールが自動的に DB コンテキストを作成し、それを DI コンテナーに登録します。

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

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_2);

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

MvcMovieContext では、Movie モデルのために EF Core 機能 (作成、読み取り、更新、削除など) が調整されます。 データ コンテキスト (MvcMovieContext) は Microsoft.EntityFrameworkCore.DbContext から派生されます。 データ コンテキストによって、データ モデルに含めるエンティティが指定されます:

// Unused usings removed.
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;  // Enables public DbSet<Movie> Movie

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

        public DbSet<Movie> Movie { get; set; }
    }
}

上記のコードによって、エンティティ セットの DbSet<Movie> プロパティが作成されます。 Entity Framework の用語では、エンティティ セットは通常はデータベース テーブルに対応します。 エンティティはテーブル内の行に対応します。

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

アプリのテスト

  • アプリを実行し、ブラウザーで URL に /Movies を追加します ( http://localhost:port/movies )。

次のようなデータベースの例外が表示された場合:

SqlException: Cannot open database "MvcMovieContext-GUID" requested by the login. The login failed.
Login failed for user 'User-name'.

移行手順を失敗しました。

  • [作成] リンクをテストします。 データを入力して送信します。

    注意

    Price フィールドに小数点のコンマを入力できない場合があります。 小数点にコンマ (",") を使う英語以外のロケール、および英語 (米国) 以外の日付形式で、jQuery 検証をサポートするには、アプリをグローバル化する必要があります。 グローバル化の手順については、この GitHub の記事をご覧ください。

  • [編集][詳細] 、および [削除] の各リンクをテストします。

次の Startup クラスを調べます。

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_2);

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

上のコードで強調表示されている部分は、依存性の注入コンテナーに追加されているムービー データベース コンテキストを示します。

  • services.AddDbContext<MvcMovieContext>(options => では、使用するデータベースと接続文字列を指定します。
  • =>ラムダ演算子です。

Controllers/MoviesController.cs ファイルを開いて、コンストラクターを調べます。

public class MoviesController : Controller
{
    private readonly MvcMovieContext _context;

    public MoviesController(MvcMovieContext context)
    {
        _context = context;
    }

コンストラクターでは、依存性の注入を使ってデータベース コンテキスト (MvcMovieContext) がコントローラーに挿入されています。 データベース コンテキストは、コントローラーの各 CRUD メソッドで使用されます。

厳密に型指定されたモデルと @model キーワード

コントローラーで ViewData ディクショナリを使ってビューにデータまたはオブジェクトを渡す方法を前に示しました。 ViewData ディクショナリは動的オブジェクトであり、ビューに情報を渡すための便利な遅延バインディングの方法を提供します。

MVC にも、厳密に型指定されたモデル オブジェクトをビューに渡す機能があります。 この厳密に型指定された方法を使うと、コンパイル時のコードのチェックが向上します。 スキャフォールディング メカニズムは、メソッドとビューを作成するときに、MoviesController クラスとビューでこの方法 (つまり、厳密に型指定されたモデルを渡すこと) を使いました。

Controllers/MoviesController.cs ファイルで生成された Details メソッドを調べてください。

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

    var movie = await _context.Movie
        .FirstOrDefaultAsync(m => m.Id == id);
    if (movie == null)
    {
        return NotFound();
    }

    return View(movie);
}

通常、id パラメーターはルート データとして渡されます。 たとえば、https://localhost:5001/movies/details/1 は次のように設定します。

  • コントローラーを movies コントローラーに (最初の URL セグメント)。
  • アクションを details に (2 番目の URL セグメント)。
  • ID を 1 に (最後の URL セグメント)。

次のようにクエリ文字列で id を渡すこともできます。

https://localhost:5001/movies/details?id=1

ID 値が指定されない場合、id パラメーターは null 許容型 (int?) として定義されます。

ルート データまたはクエリ文字列の値と一致するムービー エンティティを選択するため、ラムダ式FirstOrDefaultAsync に渡されます。

var movie = await _context.Movie
    .FirstOrDefaultAsync(m => m.Id == id);

ムービーが見つかった場合、Movie モデルのインスタンスが Details ビューに渡されます。

return View(movie);

Views/Movies/Details.cshtml ファイルの内容を確認してください。

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Movie</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.ReleaseDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.ReleaseDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Genre)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Genre)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Price)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Price)
        </dd>
    </dl>
</div>
<div>
    <a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
    <a asp-action="Index">Back to List</a>
</div>

ビュー ファイルの先頭に @model ステートメントを含めることで、ビューが期待するオブジェクトの型を指定することができます。 ムービー コントローラーを作成したとき、Details.cshtml ファイルの先頭に次の @model ステートメントが自動的に追加されています。

@model MvcMovie.Models.Movie

この @model ディレクティブにより、厳密に型指定された Model オブジェクトを使って、コントローラーがビューに渡したムービーにアクセスできます。 たとえば、Details.cshtml ビューでは、コードで厳密に型指定された Model オブジェクトを使って、DisplayNameFor および DisplayFor HTML ヘルパーに各ムービー フィールドを渡しています。 Create および Edit のメソッドとビューも、Movie モデル オブジェクトを渡します。

Movies コントローラーの Index.cshtml ビューと Index メソッドを確認してください。 コードで View メソッドを呼び出すときの List オブジェクトの作成方法に注意してください。 コードでは、この Movies リストを Index アクション メソッドからビューに渡しています。

// GET: Movies
public async Task<IActionResult> Index()
{
    return View(await _context.Movie.ToListAsync());
}

ムービー コントローラーを作成したとき、スキャフォールディングによって Index.cshtml ファイルの先頭に @model ステートメントが自動的に追加されています。

@model IEnumerable<MvcMovie.Models.Movie>

@model ディレクティブにより、厳密に型指定された Model オブジェクトを使って、コントローラーがビューに渡したムービーのリストにアクセスできます。 たとえば、Index.cshtml ビューのコードでは、foreach ステートメントを使って厳密に型指定された Model オブジェクトのムービーをループ処理しています。

@model IEnumerable<MvcMovie.Models.Movie>

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </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>

Model オブジェクトは厳密に型指定されているので (IEnumerable<Movie> オブジェクトとして)、ループ内の各項目は Movie として型指定されます。 それ以外の利点としては、コンパイル時にコードのチェックが行われます。

その他の技術情報