チュートリアル: ASP.NET MVC 5 アプリで EF を使用して継承を実装する

前のチュートリアルでは、コンカレンシー例外を処理しました。 このチュートリアルでは、データ モデルで継承を実装する方法を示します。

オブジェクト指向プログラミングでは、 継承 を使用して コードの再利用を容易にすることができます。 このチュートリアルでは、InstructorStudent クラスを Person 基底クラスから派生するように変更します。この基底クラスはインストラクターと受講者の両方に共通な LastName などのプロパティを含んでいます。 どの Web ページも追加または変更しませんが、コードの一部を変更し、それらの変更はデータベースに自動的に反映されます。

このチュートリアルでは、次の作業を行いました。

  • 継承をデータベースにマップする方法について説明します
  • Person クラスの作成
  • Instructor と Student を更新する
  • モデルにユーザーを追加する
  • 移行の作成と更新
  • 実装をテストする
  • Azure にデプロイ

前提条件

継承をデータベースにマップする

Instructorデータ モデルの School クラスと Student クラスには、同一のプロパティがいくつかあります。

Student_and_Instructor_classes

Instructor エンティティと Student エンティティで共有されるプロパティの冗長なコードを削除すると仮定します。 または、インストラクターと学生のどちらから名前を取得したかに関係なく、名前をフォーマットできるサービスを記述するとします。 これらの共有プロパティのみを含む基底クラスを作成Personし、次の図に示すように、 および Student エンティティがその基底クラスから継承されるようにInstructorすることができます。

Student_and_Instructor_classes_deriving_from_Person_class

データベースでこの継承構造を表すことができるいくつかの方法があります。 受講者とインストラクターの両方に関する情報を 1 つのテーブル内に含む Person テーブルを使用できます。 一部の列は講師 (HireDate) にのみ適用され、一部は学生 (EnrollmentDate) にのみ適用され、一部は両方に適用されます (LastNameFirstName)。 通常、各行が表す型を示す 識別子 列があります。 たとえば、識別子列にインストラクターを示す "Instructor" と受講者を示す "Student" がある場合があります。

テーブルごとのhierarchy_example

1 つのデータベース テーブルからエンティティ継承構造を生成するこのパターンは、 階層ごとのテーブル (TPH) 継承と呼ばれます。

代わりに、継承構造と同じように見えるデータベースを作成することもできます。 たとえば、Person テーブルに名前フィールドのみを含め、データ フィールドが含まれる別の Instructor テーブルと Student テーブルを使用できます。

テーブルごとのtype_inheritance

各エンティティ クラスのデータベース テーブルを作成するこのパターンは、 型ごとのテーブル (TPT) 継承と呼ばれます。

他のオプションとして、個々のテーブルにすべての非抽象型をマップすることもできます。 継承されたプロパティを含むクラスのすべてのプロパティは、対応するテーブルの列にマップされます。 このパターンは、Table-per-Concrete Class (TPC) 継承と呼ばれます。 前に示したように、PersonStudent、および Instructor クラスの TPC 継承を実装した場合、StudentInstructor のテーブルは、継承を実装した後がその前とまったく同じに見えます。

TPC と TPH の継承パターンは、通常、TPT 継承パターンよりも Entity Framework でパフォーマンスが向上します。これは、TPT パターンによって複雑な結合クエリが発生する可能性があるためです。

このチュートリアルでは、TPH 継承の実装方法を示します。 TPH は Entity Framework の既定の継承パターンであるため、クラスを作成Personし、 クラスと Student クラスを からPerson派生するように変更Instructorし、 に新しいクラスをDbContext追加し、移行を作成します。 (他の継承パターンを実装する方法については、MSDN Entity Framework ドキュメントの「 Table-Per-Type (TPT) 継承 のマッピング」および「 Table-Per-Concrete Class (TPC) 継承のマッピング 」を参照してください)。

Person クラスの作成

Models フォルダーで Person.cs を作成し、テンプレート コードを次のコードに置き換えます。

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public abstract class Person
    {
        public int ID { get; set; }

        [Required]
        [StringLength(50)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [Required]
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        public string FirstMidName { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }
    }
}

Instructor と Student を更新する

次に、 Instructor.csStudent.cs を更新して 、Person.sc から値を継承します。

Instructor.cs で、 クラスから クラスをInstructorPerson派生させ、キーフィールドと名前フィールドを削除します。 コードは次の例のように表示されます。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Instructor : Person
    {
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Hire Date")]
        public DateTime HireDate { get; set; }

        public virtual ICollection<Course> Courses { get; set; }
        public virtual OfficeAssignment OfficeAssignment { get; set; }
    }
}

Student.cs と同様の変更を行います。 クラスは Student 次の例のようになります。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Student : Person
    {
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Enrollment Date")]
        public DateTime EnrollmentDate { get; set; }

        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

モデルにユーザーを追加する

SchoolContext.cs で、エンティティ型のDbSetプロパティをPerson追加します。

public DbSet<Person> People { get; set; }

Table-per-Hierarchy 継承を構成するために Entity Framework に必要なのことはこれですべてです。 ご覧のように、データベースが更新されると、 テーブルと Instructor テーブルの代わりにテーブルがStudent作成されますPerson

移行の作成と更新

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

Add-Migration Inheritance

Update-Database PMC でコマンドを実行します。 この時点でコマンドは失敗します。移行で処理方法がわからない既存のデータがあるためです。 次のようなエラー メッセージが表示されます。

オブジェクト 'dbo を削除できませんでした。これは FOREIGN KEY 制約によって参照されるため、Instructor' です。

lt&移行を開きます。timestamp>_Inheritance.cs を使用し、 メソッドをUp次のコードに置き換えます。

public override void Up()
{
    // Drop foreign keys and indexes that point to tables we're going to drop.
    DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student");
    DropIndex("dbo.Enrollment", new[] { "StudentID" });

    RenameTable(name: "dbo.Instructor", newName: "Person");
    AddColumn("dbo.Person", "EnrollmentDate", c => c.DateTime());
    AddColumn("dbo.Person", "Discriminator", c => c.String(nullable: false, maxLength: 128, defaultValue: "Instructor"));
    AlterColumn("dbo.Person", "HireDate", c => c.DateTime());
    AddColumn("dbo.Person", "OldId", c => c.Int(nullable: true));

    // Copy existing Student data into new Person table.
    Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName, null AS HireDate, EnrollmentDate, 'Student' AS Discriminator, ID AS OldId FROM dbo.Student");

    // Fix up existing relationships to match new PK's.
    Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator = 'Student')");

    // Remove temporary key
    DropColumn("dbo.Person", "OldId");

    DropTable("dbo.Student");

    // Re-create foreign keys and indexes pointing to new table.
    AddForeignKey("dbo.Enrollment", "StudentID", "dbo.Person", "ID", cascadeDelete: true);
    CreateIndex("dbo.Enrollment", "StudentID");
}

このコードは、次のデータベースの更新タスクを処理します。

  • 外部キー制約と Student テーブルをポイントするインデックスを削除します。

  • Instructor テーブルの名前の Person に変更し、Student データを格納するために必要な変更を加えます。

    • 受講者の null 許容 EnrollmentDate を追加します。
    • 行が、受講者かインストラクターかを示すために識別子列を追加します。
    • 受講者行には雇用日がないので HireDate を nul 許容にします。
    • 受講者をポイントする外部キーの更新に使用する一時的なフィールドを追加します。 学生を Person テーブルにコピーすると、新しい主キー値が取得されます。
  • Student テーブルから Person テーブルにデータをコピーします。 これにより、受講者に新しい主キー値が割り当てられます。

  • 受講者をポイントする外部キー値を修正します。

  • 今は Person テーブルをポイントしている外部キー制約とインデックスを再作成します

(主キーの型として整数の代わりに GUID を使用した場合は、受講者の主キー値を変更する必要はなく、これらの手順のいくつかを省略できます)。

update-database コマンドをもう一度実行します。

(運用システムでは、以前のデータベース バージョンに戻るためにこれを使用する必要がある場合に備えて、Down メソッドに対応する変更を行います。このチュートリアルでは、Down メソッドを使用しません。

Note

データを移行してスキーマを変更するときに、他のエラーが発生する可能性があります。 解決できない移行エラーが発生した場合は、Web.config ファイル内の接続文字列を変更するか、データベースを削除することで、チュートリアルを続行できます。 最も簡単な方法は、 Web.config ファイル内のデータベースの名前を変更することです。 たとえば、次の例に示すように、データベース名を ContosoUniversity2 に変更します。

<add name="SchoolContext" 
    connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity2;Integrated Security=SSPI;" 
    providerName="System.Data.SqlClient" />

新しいデータベースでは、移行するデータがなく、 update-database コマンドがエラーなしで完了する可能性がはるかに高くなります。 データベースを削除する方法については、「 How to Drop a Database from Visual Studio 2012」を参照してください。 チュートリアルを続行するためにこの方法を使用する場合は、このチュートリアルの最後にある展開手順をスキップするか、新しいサイトとデータベースに展開します。 既に展開しているのと同じサイトに更新プログラムを展開すると、EF は移行を自動的に実行するときに同じエラーを受け取ります。 移行エラーのトラブルシューティングを行う場合、最適なリソースは Entity Framework フォーラムまたは StackOverflow.com のいずれかです。

実装をテストする

サイトを実行し、さまざまなページを試します。 すべてが前と同じように動作します。

[サーバー エクスプローラー] で[Data Connections\SchoolContext]、[テーブル] の順に展開すると、Student テーブルと Instructor テーブルが Person テーブルに置き換えられていることがわかります。 Person テーブルを展開すると、以前は Student テーブルと Instructor テーブルに含まれるすべての列が表示されます。

Person テーブルを右クリックし、 [テーブル データの表示] をクリックして識別子列を表示します。

次の図は、新しい School データベースの構造を示しています。

School_database_diagram

Azure にデプロイ

このセクションでは、このチュートリアル シリーズのパート 3、並べ替え、フィルター処理、ページングに関するページのオプションの「Azure へのアプリのデプロイ」セクションを完了している必要があります。 ローカル プロジェクトでデータベースを削除して解決した移行エラーが発生した場合は、この手順をスキップします。または、新しいサイトとデータベースを作成し、新しい環境に展開します。

  1. Visual Studio のソリューション エクスプローラーで、プロジェクトを右クリックし、コンテキスト メニューの [発行] をクリックします。

  2. [発行] をクリックします。

    既定のブラウザーで Web アプリが開きます。

  3. アプリケーションをテストして、動作していることを確認します。

    データベースにアクセスするページを初めて実行すると、Entity Framework は、現在のデータ モデルでデータベースを最新の状態にするために必要なすべての移行 Up 方法を実行します。

コードを取得する

完成したプロジェクトのダウンロード

その他のリソース

他の Entity Framework リソースへのリンクは、「 ASP.NET データ アクセス - 推奨リソース」にあります。

この継承構造とその他の継承構造の詳細については、MSDN の 「TPT 継承パターン 」および 「TPH 継承パターン 」を参照してください。 次のチュートリアルでは、比較的高度なさまざまな Entity Framework のシナリオを処理する方法を説明します。

次の手順

このチュートリアルでは、次のことを行いました。

  • 継承をデータベースにマップする方法について学習しました
  • Person クラスを作成した
  • Instructor と Student を更新した
  • モデルにユーザーを追加しました
  • 移行の作成と更新
  • 実装をテストした
  • Azure にデプロイ済み

次の記事に進み、Entity Framework Code First を使用する web アプリケーションの開発の基本 ASP.NET 超える場合に注意するのに役立つトピックについて説明します。