新しいフィールドの追加

作成者: Rick Anderson

Note

Visual Studio の最新バージョンを使用した、このチュートリアルの更新バージョンはこちらにあります。 新しいチュートリアル (このチュートリアルよりも多くの改善がされています) では、ASP.NET Core MVC を使用しています。

このチュートリアルでは、ASP.NET Core MVC のコントローラーとビューについて説明します。 Razor Pages は ASP.NET Core での新しい代替手段であり、Web UI の構築をより簡単かつ生産的にする、ページベースのプログラミング モデルです。 MVC のバージョンの前に、Razor ページのチュートリアルを試すことをお勧めします。 この Razor ページのチュートリアルの特徴は次のとおりです。

  • 使いやすい。
  • 多くの機能をカバーしている。
  • 新しいアプリ開発には、これが最適のアプローチです。

このセクションでは、Entity Framework Code First Migrations を使用してモデル クラスにいくつかの変更を移行し、変更がデータベースに適用されるようにします。

このチュートリアルの前の方でしたように、Entity Framework の Code First を使用してデータベースを自動作成すると、既定で Code First がテーブルをデータベースに追加して、データベースのスキーマがその生成元であるモデル クラスと同期されているかを追跡するようになります。 もし同期されていない場合、Entity Framework はエラーをスローします。 これにより、実行時にあいまいなエラーが表示されて初めて気づくような問題を、開発段階で容易に追跡できるようになります。

モデル変更用の Code First Migrations の設定

ソリューション エクスプローラーに移動します。 Movies.mdf ファイルを右クリックし、[削除] を選択してムービー データベースを削除します。 Movies.mdf ファイルが表示されない場合は、下に赤いアウトラインで示されている [すべてのファイルを表示] アイコンを選択します。

[Movies Controller dot c s]\(ムービー コントローラードット c s\) タブを示すスクリーンショットソリューション エクスプローラー開いています。[すべてのファイルを表示] アイコンは赤で囲まれています。

アプリケーションをビルドしてエラーがないことを確認します。

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

パックマンの追加

[パッケージ マネージャー コンソール] ウィンドウの PM> プロンプトで、次のように入力します

Enable-Migrations -ContextTypeName MvcMovie.Models.MovieDBContext

パッケージ マネージャー コンソール ウィンドウを示すスクリーンショット。[移行の有効化] コマンドのテキストが強調表示されています。

Enable-Migrations コマンド (上記) は、新しい Migrations フォルダーに Configuration.cs ファイルを作成します。

ソリューション エクスプローラーを示すスクリーンショット。移行フォルダーの構成ドット c s サブフォルダーが選択されています。

Visual Studio によって Configuration.cs ファイルが開かれます。 Configuration.cs ファイル内の Seed メソッドを次のコードに置き換えます。

protected override void Seed(MvcMovie.Models.MovieDBContext context)
{
    context.Movies.AddOrUpdate( i => i.Title,
        new Movie
        {
            Title = "When Harry Met Sally",
            ReleaseDate = DateTime.Parse("1989-1-11"),
            Genre = "Romantic Comedy",
            Price = 7.99M
        },

         new Movie
         {
             Title = "Ghostbusters ",
             ReleaseDate = DateTime.Parse("1984-3-13"),
             Genre = "Comedy",
             Price = 8.99M
         },

         new Movie
         {
             Title = "Ghostbusters 2",
             ReleaseDate = DateTime.Parse("1986-2-23"),
             Genre = "Comedy",
             Price = 9.99M
         },

       new Movie
       {
           Title = "Rio Bravo",
           ReleaseDate = DateTime.Parse("1959-4-15"),
           Genre = "Western",
           Price = 3.99M
       }
   );
   
}

Movieの下の赤い波線にカーソルを合わせ、[Show Potential Fixes] をクリックし、[using MvcMovie.Models; をクリックします。

[潜在的な修正プログラムの表示] メニューを示すスクリーンショット。M V C Movie ドット モデルの使用が選択され、見つからないアラートが表示されます。

これにより、次の using ステートメントが追加されます。

using MvcMovie.Models;

Note

Code First Migrations は、移行が終わるたびに Seed メソッドを呼び出し (つまり、パッケージ マネージャー コンソールで update-database を呼び出す)、このメソッドは既に挿入されている行を更新するか、まだ存在しない場合は挿入します。

次のコードの AddOrUpdate メソッドは、"upsert" 操作を実行します。

context.Movies.AddOrUpdate(i => i.Title,
    new Movie
    {
        Title = "When Harry Met Sally",
        ReleaseDate = DateTime.Parse("1989-1-11"),
        Genre = "Romantic Comedy",
        Rating = "PG",
        Price = 7.99M
    }

Seed メソッドは移行ごとに実行されるため、単にデータを挿入することはできません。なぜなら、追加しようとしている行はデータベースを作成する最初の移行の後に既に存在するためです。 "upsert" 操作は、既に存在する行を挿入しようとした場合に発生するエラーを防ぎますが、アプリケーションのテスト中に行った可能性のあるデータに対する変更をオーバーライドします。 一部のテーブルのテスト データではデータを変更したくない場合があります。テスト中にデータを変更した場合、データベースの更新後も変更を維持したいことがあります。 その場合は、条件付き挿入操作を実行します。行がまだ存在しない場合にのみ行を挿入します。

AddOrUpdate メソッドに渡される最初のパラメーターは、行が既に存在するかどうかを確認するために使用するプロパティを指定します。 指定するテスト映画データの場合、リスト内のタイトルはそれぞれ一意であるため、Title プロパティをこの目的に使用できます。

context.Movies.AddOrUpdate(i => i.Title,

このコードは、タイトルが一意であることを前提としています。 重複するタイトルを手動で追加すると、次に移行を実行するときに次の例外が発生します。

シーケンスに複数の要素が含まれています

AddOrUpdate メソッドの詳細については、「Take care with EF 4.3 AddOrUpdate Method (EF 4.3 AddOrUpdate メソッドに注意する)」を参照してください。

Ctrl + Shift + B キーを押して、プロジェクトをビルドします。(この時点でビルドしないと、次の手順は失敗します)。

次の手順では、最初の移行用の DbMigration クラスを作成します。 この移行により、新しいデータベースが作成されます。そのため、前の手順で movie.mdf ファイルを削除しました。

[パッケージ マネージャー コンソール] ウィンドウで、add-migration Initial コマンドを入力して最初の移行を作成します。 "Initial" という名前は任意です。作成される移行ファイルに名前を付けるために使用します。

パッケージ マネージャー コンソールを示すスクリーンショット。移行の追加コマンドのテキストが強調表示されています。

Code First Migrations は Migrations フォルダーに ({DateStamp}_Initial.cs という名前で) 別のクラス ファイルを作成します。このクラスにはデータベース スキーマを作成するコードが含まれています。 移行のファイル名の先頭には、並べ替えに役立つタイムスタンプが付きます。 {DateStamp}_Initial.cs ファイルを調べます。これには、Movie DB の Movies テーブルを作成する手順が含まれています。 次の手順でデータベースを更新すると、この {DateStamp}_Initial.cs ファイルが実行され、DB スキーマが作成されます。 次に、Seed メソッドが実行され、テスト データが DB に設定されます。

[パッケージ マネージャー コンソール] で、update-database コマンドを入力してデータベースを作成し、Seed メソッドを実行します。

パッケージ マネージャー コンソールを示すスクリーンショット。データベースの更新コマンドがウィンドウ内にあります。

テーブルが既に存在し、作成できないことを示すエラーが発生した場合は、データベースを削除した後で、update-database を実行する前にアプリケーションを実行したためである可能性があります。 その場合は Movies.mdf ファイルをもう一度削除し、update-database コマンドを再試行してください。 それでもエラーが発生する場合は、移行フォルダーとコンテンツを削除し、このページの上部にある手順から始めます (つまり、Movies.mdf ファイルを削除してから Enable-Migrations に進みます)。 それでもエラーが発生する場合は、SQL Server オブジェクト エクスプローラーを開き、一覧からデータベースを削除します。 "ファイル .mdf をデータベースとしてアタッチできません" というエラーが発生した場合は、web.config ファイルの接続文字列の一部として初期カタログ プロパティを削除します。

アプリケーションを実行し、/Movies URL に移動します。 シード データが表示されます。

4 つの映画が一覧表示されている M V C ムービー インデックスを示すスクリーンショット。

ムービー モデルへの評価プロパティの追加

まず、新しい Rating プロパティを既存の Movie クラスに追加します。 Models\Movie.cs ファイルを開き、次のように Rating プロパティを追加します。

public string Rating { get; set; }

完成した Movie クラスは次のコードのようになります。

public class Movie
{
    public int ID { get; set; }
    public string Title { get; set; }

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; }
    public decimal Price { get; set; }
    public string Rating { get; set; }
}

アプリケーションをビルドします (Ctrl キーと Shift キーを押しながら B キーを押します)。

新しいフィールドを Movie クラスに追加したので、この新しいプロパティが含まれるように、拘束力のある許可リストを更新する必要もあります。 CreateEdit の両方のアクション メソッドの bind 属性を更新して、Rating プロパティが含まれるようにします。

[Bind(Include = "ID,Title,ReleaseDate,Genre,Price,Rating")]

ブラウザー ビューで新しい Rating プロパティを表示、作成、編集する目的でビュー テンプレートを更新する必要もあります。

\Views\Movies\Index.cshtml ファイルを開き、<th>Rating</th> 列見出しを Price 列の直後に追加します。 次に、@item.Rating 値をレンダリングするために、テンプレートの末尾付近に <td> 列を追加します。 更新した Index.cshtml ビュー テンプレートは次のようになります。

@model IEnumerable<MvcMovie.Models.Movie>
@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
    @Html.ActionLink("Create New", "Create")
    @using (Html.BeginForm("Index", "Movies", FormMethod.Get))
    {
    <p>
        Genre: @Html.DropDownList("movieGenre", "All")
        Title: @Html.TextBox("SearchString")
        <input type="submit" value="Filter" />
    </p>
    }
</p>
<table class="table">
    <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>
            @Html.DisplayNameFor(model => model.Rating)
        </th>

        <th></th>
    </tr>

@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>
            @Html.DisplayFor(modelItem => item.Rating)
        </td>

        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
            @Html.ActionLink("Details", "Details", new { id=item.ID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.ID })
        </td>
    </tr>
}

</table>

次に、\Views\Movies\Create.cshtml ファイルを開き、次のマークアップが強調表示された Rating フィールドを追加します。 これによってテキスト ボックスがレンダリングされ、新しいムービーが作成されたときに評価を指定できるようになります。

<div class="form-group">
            @Html.LabelFor(model => model.Price, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Price, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Price, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Rating, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Rating, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Rating, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

アプリケーション コードを、新しい Rating プロパティをサポートするように更新しました。

アプリケーションを実行し、/Movies URL に移動します。 ただし、これを行うと次のエラーのいずれかが表示されます。

例外ユーザーのハンドルされないエラーを示すスクリーンショット。

データベースの作成後、'MovieDBContext' コンテキストの背後にあるモデルが変更されました。 Code First Migrations を使用したデータベースの更新を検討してください (https://go.microsoft.com/fwlink/?LinkId=238269)。

アプリケーションの通知サーバー エラーが表示されたブラウザーを示すスクリーンショット。

このエラーが表示されるのは、更新された Movie モデル クラスが既存のデータベースの Movie テーブルのスキーマと異なるためです。 (データベース テーブルに Rating 列はありません)。

このエラーを解決するための手法がいくつかあります。

  1. Entity Framework に、新しいモデル クラス スキーマに基づいてデータベースを自動的にドロップさせ、再作成させます。 この手法は、開発周期の早い段階で、テスト データベースで開発しているときに非常に便利です。モデルとデータベース スキーマを一緒に短期間で発展させることができます。 ただし欠点もあり、データベースの既存データが失われます。実稼働データベースではこの手法は推奨されません。 初期化子を利用し、データベースにテスト データを自動的に初期投入します。多くの場合、アプリケーション開発の手法として有益な方法です。 Entity Framework データベース初期化子の詳細については、ASP.NET MVC/Entity Framework チュートリアルを参照してください。
  2. モデル クラスに一致するように、既存のデータベースのスキーマを明示的に変更します。 この手法の長所は、データが維持されることです。 この変更は手動で行うことも、データベース変更スクリプトを作成して行うこともできます。
  3. Code First Migrations を使用して、データベース スキーマを更新します。

このチュートリアルでは、Code First Migrations を利用します。

新しい列に値を提供するように、Seed メソッドを更新します。 Migrations\Configuration.cs ファイルを開き、各 Movie オブジェクトに Rating フィールドを追加します。

new Movie
{
    Title = "When Harry Met Sally",
    ReleaseDate = DateTime.Parse("1989-1-11"),
    Genre = "Romantic Comedy",
    Rating = "PG",
    Price = 7.99M
},

ソリューションをビルドした後、[パッケージ マネージャー コンソール] ウィンドウを開き、次のコマンドを入力します。

add-migration Rating

add-migration コマンドは、現在のムービー DB スキーマを使用して現在のムービー モデルを調べ、新しいモデルに DB を移行するために必要なコードを作成するように移行フレームワークに指示します。 Rating (評価) という名前は任意です。移行ファイルに名前を付けるために利用されます。 移行ステップにはわかりやすい名前を使用すると便利です。

このコマンドが完了すると、Visual Studio は新しい DbMigration 派生クラスを定義するクラス ファイルを開き、Up メソッドで新しい列を作成するコードを確認できます。

public partial class AddRatingMig : DbMigration
{
    public override void Up()
    {
        AddColumn("dbo.Movies", "Rating", c => c.String());
    }
    
    public override void Down()
    {
        DropColumn("dbo.Movies", "Rating");
    }
}

ソリューションをビルドし、[パッケージ マネージャー コンソール] ウィンドウで update-database コマンドを入力します。

次の図は、[パッケージ マネージャー コンソール] ウィンドウの出力を示しています (Rating の前の日付スタンプは異なります)。

データベースの更新コマンドが入力された [パッケージ マネージャー コンソール] ウィンドウを示すスクリーンショット。

アプリケーションを再実行し、/Movies の URL に移動します。 新しい [Rating] フィールドが表示されます。

[評価] フィールドが追加された [M V C Movie Index] リストを示すスクリーンショット。

[Create New] リンクをクリックして、新しい映画を追加します。 評価も追加できます。

7_CreateRioII

Create をクリックしてください。 評価を含む新しいムービーが、ムービー一覧に表示されます。

7_ourNewMovie_SM

プロジェクトで移行が使用されるようになったので、新しいフィールドを追加したり、スキーマを更新したりするときにデータベースを削除する必要はなくなります。 次のセクションでは、スキーマをさらに変更し、移行を使用してデータベースを更新します。

また、Edit、Details、Delete ビュー テンプレートに Rating フィールドを追加する必要もあります。

[パッケージ マネージャー コンソール] ウィンドウに "update-database" コマンドをもう一度入力しても、スキーマがモデルと一致するため、移行コードは実行されません。 ただし、"update-database" を実行すると Seed メソッドがもう一度実行され、シード データのいずれかを変更した場合、Seed メソッドがデータをアップサートするため、変更は失われます。 Seed メソッドの詳細については、Tom Dykstra による人気の ASP.NET MVC/Entity Framework チュートリアルを参照してください。

このセクションでは、モデル オブジェクトを変更し、データベースをその変更に合わせて同期させる方法を学びました。 また、新しく作成されたデータベースにサンプル データを入力してシナリオを試してみる方法についても学習しました。 これは、Code First の簡単な概要でした。このテーマに関するより詳細なチュートリアルについては、ASP.NET MVC アプリケーション用の Entity Framework データ モデルの作成に関するページを参照してください。 次に、モデル クラスに高度な検証ロジックを追加して、ビジネス ルールを適用できるようにする方法を学びましょう。