ASP.NET Core MVCのチュートリアルを丁寧にやってみた②(モデルの追加とDBの作成)

ASP.NET Core

ASP.NET Core MVCの公式チュートリアルを丁寧に解説する記事の第2回です。

第1回では、プロジェクトの作成からコントローラーやビューの仕組みなどについて確認しました。

第2回では、MVCアーキテクチャにおける「モデル」を作成し、Entity Framework Coreを使ってDBと連携する方法までを見ていきます。

公式チュートリアルのパート4~5の内容に対応します。

パート 4、ASP.NET Core MVC アプリにモデルを追加する
ASP.NET Core MVC のチュートリアル シリーズのパート 4。
スポンサーリンク

Entity Framework Coreを使ったモデルの追加

前回はMVCのビューとコントローラーを作成して、それらの基本的な動作について確認しました。

今回はアプリの中心となるロジックを担うモデルを、Entity Framework Core(以下EFCore)というO/Rマッパーを使って追加します。

O/Rマッパー(ORM)とは、アプリケーションとデータベース間の差異を吸収することで、データベースへの接続や操作を簡単にしてくれる便利なツールです。

ASP.NET CoreではEFCoreが標準搭載されており、これを使うことでSQLを書くことなく、C#のコードだけでデータベースにアクセスできます。

EFCoreのインストール

まずはプロジェクトにEFCoreをインストールしましょう。
「ツール」→「NuGetパッケージマネージャー」→「パッケージマネージャーコンソール」でコンソールを開きます。

パッケージマネージャーコンソール画面で次のコマンドを実行すると、EFCoreがインストールされます。

Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.SqlServer

データモデルクラスの定義

EFCoreがインストールできたら、データベースのテーブルの元となるデータモデルクラスを定義します。

「Models」フォルダを右クリックし、「追加」→「クラス」で「Movie.cs」という名前のファイルを追加し、次のコードに書き換えます。

using System.ComponentModel.DataAnnotations;

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

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
    }
}

データモデルクラスのポイントは次の3点です。

  • クラス名とテーブル名を一致させる
  • 各プロパティとテーブルの列名と一致させる
  • Idプロパティがテーブルの主キーとなる

なお、[DataType]属性を付けることで表示される形式(型)を指定することができ、ここではDate(日付型)を指定しているので時刻が画面に表示されなくなります。

※stringの後ろに付いている疑問符(?)は、そのプロパティが Null 許容であることを示すものです。ここでは詳細については触れませんので、もし気になる場合は下記ドキュメントを参照してください。

null 許容参照型
この記事では、C# 8.0 で追加された null 許容参照型の概要を説明します。 新規および既存のプロジェクトにおいて、その機能によって null 参照例外に対する安全性がどのように提供されるかを学習します。

スキャフォールディング

スキャフォールディングとは、CRUD(Create, Read, Update, Delete)機能を備えたプログラムを自動で生成してくれる便利な機能です。

Controllersフォルダを右クリックして、「追加」→「新規スキャフォールディングアイテム」をクリックします。

「Entity Frameworkを使用したビューがあるMVCコントローラー」を追加します。

モデルクラスには先ほど作成した「Movie」クラスを選択します。

データコンテキストクラスはまだ作成していないので、右側の「+」ボタンをクリックして、デフォルトで表示される「MvcMovieContext」を追加します。
※データコンテキストクラスとは、単なるクラスであるモデルクラスとデータベースの関連付けを行うクラスです。

その他の設定はデフォルトのままで大丈夫です。

「追加」をクリックすると、スキャフォールディングが開始されます。

※もしスキャフォールディング実行時にエラーが発生した場合は、スキャフォールディングを再実行してみてください。

スキャフォールディングが完了すると、データコンテキストを登録するためのコードがProgram.csに、DBに接続するための文字列がappsettings.jsonに、それぞれ自動で追加されます。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovieContext") ?? throw new InvalidOperationException("Connection string 'MvcMovieContext' not found.")));

    <以下略>
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "MvcMovieContext": "Server=(localdb)\\mssqllocaldb;Database=MvcMovie.Data;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

(参考)接続文字列の内容

  • Server:接続先のSQL Server。今回はVisual Studioに標準搭載されているLocalDBを指定
  • Database:データベース名を指定。存在しなければ指定した名前で作成する
  • Trusted_Connection:trueはWindows認証、falseはSQL Server認証
  • MultipleActiveResultSets:trueにすると1つの接続で複数の結果セットを取得することが可能になる

また、以下の赤枠のファイルが自動で追加され、CRUD操作ができるようになります。

ただし現時点ではデータベースがまだ作成されていないため、CRUD操作を行おうとしてもエラーになります。

データベースを作成するには次のマイグレーションを行う必要があります。

マイグレーション

マイグレーションとは、データモデルクラスに合わせてデータベース(テーブル)を作成したり、更新することができる機能です。今回は初回なので、実行するとデータベースが作成されます。

「ツール」→「NuGetパッケージマネージャー」→「パッケージマネージャーコンソール」でコンソールを開き、以下のコマンドを実行してみましょう。

Add-Migration InitialCreate
Update-Database

Add-Migrationコマンドはデータモデルクラスからマイグレーションファイルを作成します。
InitialCreateはマイグレーションファイルの名前で、慣例でマイグレーションの中身を説明するような名前を付けます。

その後Update-Databaseコマンドを実行することで、マイグレーションファイルの内容がデータベースに反映されます。

なお実行後にこのような警告が出ますが、後で修正するので無視してOKです。

マイグレーションが成功するとプロジェクト直下にMigrationsフォルダが作成されます。
その中に「<日付と時刻>_InitialCreate.cs」というファイルが作成されていることを確認しておきましょう。

アプリを実行してCRUD操作を試す

それではF5でデバッグ実行して動作を確認してみましょう。

https://localhost:<ポート番号>/movies にアクセスすると次のように表示されるはずです。

試しに「Create New」をクリックして適当なデータを入力後、「Create」ボタンを押してみます。

登録が成功するとIndexページが表示され、今登録した内容が反映されます。

「Details」をクリックすると詳細画面が表示されます。

起動時のURLの変更

以後は今回作成したMoviesControllerにアクセスすることになるので、Program.csを編集して起動時のURLを変更しておきましょう。

<省略>

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Movies}/{action=Index}/{id?}");

app.Run();

コントローラーからビューにモデルを渡す流れを確認

第1回では、コントローラからビューに値を渡す際にViewDataを使用しましたが、スキャフォールディングで作られたファイルでは厳密に型指定されたモデルを渡しています。

具体的にどのようにコントローラーからビューにモデルが渡されているのかを確認してみましょう。

まずMoviesControllerのDetailsメソッドを見てみます。

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

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

            return View(movie);
        }

最初に引数としてidを受け取り、FirstOrDefaultAsyncメソッドによって、受け取ったidとMovieテーブルのIdが一致する単一のレコードを取得します。

取得した結果をモデルクラス(Movie型の変数)に格納し、中身がnullでなければViewメソッドでモデルをビューに渡します。

次に、モデルが渡されてくる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.Genre)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.Genre)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.Revenue)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.Revenue)
        </dd>
        <dt class = "col-sm-2">
            @Html.DisplayNameFor(model => model.ReleaseDate)
        </dt>
        <dd class = "col-sm-10">
            @Html.DisplayFor(model => model.ReleaseDate)
        </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ディレクティブで、このビューで使用するモデルを指定しています。こうすることでインテリセンスや型チェックが機能するようになります。

さらにDisplayNameForやDisplayForといったHTMLヘルパーを使うことで、モデルの型に適した形式でビューに表示することができます。
(例:bool型ならチェックボックスで表示、日付型なら入力欄にカレンダーウィジェットを表示させるなど)

ちなみに現時点ではMovie.csでDisplayName属性を何も指定していないのでそのままのプロパティ名が表示されますが、指定していればその名前がDisplayNameForで表示されることになります。

テーブルの中身を確認

最後に、今回作成されたLocalDBのテーブルを確認してみましょう。
「表示」→「SQL Server オブジェクト エクスプローラー」をクリックします。

エクスプローラーが開いたら、「(localdb)\MSSQLLocalDB~」→「データベース」→「MvcMovie.Data」→「テーブル」とツリーを展開し、dbo.Movieを右クリックして「データの表示」をクリックします。

Movieテーブルの内容が表示され、先ほど登録したデータが反映されていることが確認できます。

まとめ

Entity Framework Coreを始めて使う際に必要な操作を簡単にまとめてみます。

  1. NuGetパッケージマネージャーからEFCoreをインストール
  2. テーブルの元となるデータモデルクラスを定義
  3. スキャフォールディングを実行してCRUD機能を自動生成
  4. マイグレーションを実行してDBの作成と反映を行う

これで基本的なCRUD機能を備えたアプリケーションができます。

※もしデータモデルクラスのプロパティを変更したり、追加した場合は再度マイグレーションを行ってDBを更新する必要があります。

第3回では、ビューのタグヘルパーやコントローラーのPOSTリクエストの処理についてより詳しく見るとともに、新たに検索機能の追加を行っていきます。

コメント

  1. 部長 より:

    凄く解りやすいです!!
    難解な公式サイトの掲載と入れ替えて欲しい位です(^^;
    秀でた解説の才能をお持ちで羨ましい限り。
    ここはどうなってるのだろう?という箇所も解説頂けており「おお!」と興奮ならが理解出来ました。

    本当にありがとうございます!

    お礼ついでに、補足情報を書き込ませて下さい。

    Install-Package Microsoft.EntityFrameworkCore.SqlServer
    の個所で、

    Install-Package : NU1202: パッケージ Microsoft.EntityFrameworkCore.Tools 6.0.2 は netcoreapp3.1 (.NETCoreApp,Version=v3.1) と互換性がありません。 パッケージ Microsoft.EntityFrameworkCore.Tools 6.0.2 がサポートするもの: net6.0 (.NETCoreApp,Version=v6.0)
    発生場所 行:1 文字:1

    となったので↓で解決させました。
    Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 5.0.12
    Install-Package Microsoft.EntityFrameworkCore.Design -Version 5.0.12

    無印だとnet6.0を取得しに行くVisualStudio2022仕様になったのかも?

    • ひらひら より:

      とても嬉しいお言葉をありがとうございます!
      元々は自分の勉強メモのような形から書き始めたものですが、結果的にお役に立てたようで大変嬉しいです。
      今後もできるだけわかりやすい記事を書いていきますので、どうぞよろしくお願いします。

      補足情報についてもありがとうございます!
      バージョンを指定しないでパッケージをインストールすると.NET6用の最新バージョン(6.0.0~)がインストールされてしまい、ご指摘の通り互換性がないと怒られてしまいますね…
      記事の内容が最新の環境に対応しておらず、ご不便をおかけしてしまい申し訳ありませんでした。
      早速該当箇所にその旨を追記させていただきました。

  2. 名前 より:

    おそらく現在同じような手順を踏むと、Startup.csではなくProgram.csが生成されると思います。
    その場合はどのように記述するのでしょうか。
    公式ドキュメントのほうでもStartup.csから更新されておらず、そこで止まってしまっています。

タイトルとURLをコピーしました