記事内に広告が含まれています

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

C#

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

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

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

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

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

モデルの追加

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

今回はアプリの中心となるロジックを担うモデルを追加していきます。

Entity Framework Core とは

まず前提として、今回使用するEntity Framework Core(以下EF Core)について簡単に説明します。

EF CoreとはMicrosoftが提供する公式のO/Rマッパー(ORM)です。

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

これを使うことでSQLを書くことなく、C#のコードだけでデータベースにアクセスすることができます。

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

それでは、データベースのテーブルの元となるデータモデルクラスを定義します。

「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 許容参照型

スキャフォールディング

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

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

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

モデルクラスとDbContext クラスを選択する画面が表示されるので、以下の内容を指定します。

  • モデル クラス:「Movie」 (先ほど作成したクラス)を選択
  • DbContext クラス:右側の「+」ボタンをクリックして、デフォルトで表示され「MvcMovie.Data.MvcMovieContext」を追加
    ※DbContext クラスとは、モデルクラスとデータベースの関連付けを行うクラスです。
  • データベース プロバイダー:「SQL Server」

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

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

このタイミングでEF Core関連のNuGetパッケージも自動でプロジェクトに追加されます。

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

スキャフォールディングが完了すると、DbContextをアプリに登録するためのコードが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に標準搭載されているLocal DBが指定される
  • Database:データベース名を指定。存在しなければ指定した名前で作成する
  • Trusted_Connection:trueはWindows認証、falseはSQL Server認証
  • MultipleActiveResultSets:trueにすると1つの接続で複数の結果セットを取得することが可能になる

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

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

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

マイグレーション

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

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

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を使用しましたが、スキャフォールディングで作られたファイルでは厳密に型指定されたモデルを渡しています。

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

まずMoviesControllerDetailsメソッドを見てみます。

        // 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を受け取り、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ディレクティブで、このビューで使用するモデルを指定しています。こうすることでインテリセンスや型チェックが機能するようになります。

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

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

テーブルの中身を確認

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

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

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

おわりに

今回は、データモデルクラスの定義、スキャフォールディングによるCRUD機能の生成、EF CoreのマイグレーションによるDB作成などを行いました。

スキャフォールディングとEF Coreを使えば簡単にCRUD機能を備えたアプリケーションが作れることが確認できたかと思います。

第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から更新されておらず、そこで止まってしまっています。

  3. より:

    すみません、質問なのですが
    MoviesController.csに

    if (ModelState.IsValid)
    {
    _logger.Add(user);
    await _logger.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
    }

    という部分がありますよね?
    これっておそらく、Movieを登録した後にIndexにリダイレクトするといった意味を
    持っていると思うのですが、登録後にそのIdプロパティをURLに含んだページに
    リダイレクトするように改変する事ってできますかね…?

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