Code First Data Annotations (Code First のデータ注釈)

Note

EF4.1 以降のみ - このページで説明されている機能や API などは、Entity Framework 4.1 で導入されました。 以前のバージョンを使用している場合、この情報の一部またはすべてが当てはまりません。

このページの内容は、もともと Julie Lerman (<http://thedatafarm.com>) によって書かれた記事から改作されています。

Entity Framework の Code First を使用すると、独自のドメイン クラスを使用して、クエリ、変更の追跡、関数の更新を実行するために EF が依存するモデルを表現できます。 Code First では、"構成を支配する規則" と呼ばれるプログラミング パターンが利用されます。 Code First では、クラスは Entity Framework の規則に従うと想定されており、その場合、ジョブの実行方法は自動的に解決されます。 ただし、クラスがそれらの規則に従っていない場合は、必要な情報を EF に提供するために、クラスに構成を追加する機能が用意されています。

Code First には、これらの構成をクラスに追加する方法が 2 つ備わっています。 1 つは、DataAnnotations と呼ばれる単純な属性を使用するもので、2 つ目は、構成をコード内に強制的に記述する方法を提供する、Code First の Fluent API を使用するものです。

この記事では (System.ComponentModel.DataAnnotations 名前空間の) DataAnnotations を使用してクラスを構成することに焦点を当てて、最もよく必要とされる構成を取り上げます。 DataAnnotations は、同じ注釈をクライアント側の検証に利用できるようにする、ASP.NET MVC などの多くの .NET アプリケーションでも認識されます。

モデル

単純な 1 組のクラスである Blog と Post を使用して、Code First の DataAnnotations の実例を示します。

    public class Blog
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string BloggerName { get; set;}
        public virtual ICollection<Post> Posts { get; set; }
    }

    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public DateTime DateCreated { get; set; }
        public string Content { get; set; }
        public int BlogId { get; set; }
        public ICollection<Comment> Comments { get; set; }
    }

Blog クラスと Post クラスは、便利なことにそのままで Code First の規則に従うので、EF の互換性を有効にするための調整は必要ありません。 ただし、注釈を使用して、マップ先のクラスとデータベースに関するより詳細な情報を EF に提供することもできます。

 

キー

Entity Framework は、エンティティの追跡に使用されるキー値を持つすべてのエンティティに依存します。 Code First の 1 つの規則は、暗黙的なキー プロパティです。Code First では、"Id" という名前のプロパティ、またはクラス名と "Id" ("BlogId" など) との組み合わせが検索されます。 このプロパティは、データベースの主キー列にマップされます。

Blog クラスと Post クラスは、どちらもこの規則に従います。 そうでなければ、どうなるでしょうか。 Blog で、代わりに PrimaryTrackingKey という名前や foo が使用された場合はどうなるでしょうか。 Code First で、この規則に一致するプロパティが見つからない場合、キー プロパティが必要であるという Entity Framework の要件のために、例外がスローされます。 キー注釈を使用して、どのプロパティを EntityKey として使用するかを指定できます。

    public class Blog
    {
        [Key]
        public int PrimaryTrackingKey { get; set; }
        public string Title { get; set; }
        public string BloggerName { get; set;}
        public virtual ICollection<Post> Posts { get; set; }
    }

Code First のデータベース生成機能を使用している場合、Blog テーブルには、PrimaryTrackingKey という名前の主キー列が作成されます。これは、既定で ID として定義されます。

Blog table with primary key

複合キー

Entity Framework では、複合キーがサポートされています。これは、複数のプロパティで構成される複数の主キーです。 たとえば、主キーが PassportNumber と IssuingCountry の組み合わせである Passport クラスを作成できます。

    public class Passport
    {
        [Key]
        public int PassportNumber { get; set; }
        [Key]
        public string IssuingCountry { get; set; }
        public DateTime Issued { get; set; }
        public DateTime Expires { get; set; }
    }

EF モデルで上記のクラスを使おうとすると、InvalidOperationException が発生します。

型 'Passport' の複合主キーの順序を決定できません。 ColumnAttribute または HasKey メソッドを使用して、複合主キーの順序を指定してください。

複合キーを使用するには、Entity Framework でキー プロパティの順序を定義する必要があります。 これは、Column 注釈を使用して順序を指定することで行えます。

Note

順序の値は (インデックスベースではなく) 相対的なので、任意の値を使用できます。 たとえば、1 と 2 の代わりに、100 と 200 でもかまいません。

    public class Passport
    {
        [Key]
        [Column(Order=1)]
        public int PassportNumber { get; set; }
        [Key]
        [Column(Order = 2)]
        public string IssuingCountry { get; set; }
        public DateTime Issued { get; set; }
        public DateTime Expires { get; set; }
    }

複合外部キーを持つエンティティがある場合は、対応する主キーのプロパティに対して使用したのと同じ列順序を指定する必要があります。

外部キーのプロパティ内での相対的順序だけが同じである必要があり、Order に割り当てられた値そのものが一致している必要はありません。 たとえば次のクラスでは、1 と 2 の代わりに 3 と 4 を使用できます。

    public class PassportStamp
    {
        [Key]
        public int StampId { get; set; }
        public DateTime Stamped { get; set; }
        public string StampingCountry { get; set; }

        [ForeignKey("Passport")]
        [Column(Order = 1)]
        public int PassportNumber { get; set; }

        [ForeignKey("Passport")]
        [Column(Order = 2)]
        public string IssuingCountry { get; set; }

        public Passport Passport { get; set; }
    }

必須

Required 注釈では、特定のプロパティが必要であることを EF に指示します。

Title プロパティに Required を追加すると、EF (と MVC) は、プロパティ内にデータがあることを確認するよう強制されます。

    [Required]
    public string Title { get; set; }

アプリケーションでコードの追加やマークアップの変更を行わなくても、MVC アプリケーションによってクライアント側の検証が実行され、メッセージの動的作成さえも、プロパティと注釈の名前を使用して行われます。

Create page with Title is required error

Required 属性は、マップされたプロパティを null 非許容にすると、生成されるデータベースにも影響するようになります。 [タイトル] フィールドが、"not null" に変化したことに注目してください。

Note

ときには、プロパティが必要であっても、データベース内の列を null 非許容にすることができない場合があります。 たとえば、TPH 継承戦略の使用時には、複数の型のデータが 1 つのテーブルに格納されます。 派生型に必須プロパティが含まれる場合、階層内のすべての型がこのプロパティを持つわけではないため、列を null 非許容にすることができません。

 

Blogs table

 

MaxLength と MinLength

MaxLength 属性と MinLength 属性を使用すると、Required を使用して行ったのと同様に、追加のプロパティの検証を指定できます。

次に示すのは、要件 Length がある BloggerName です。 この例は、属性を結合する方法も示しています。

    [MaxLength(10),MinLength(5)]
    public string BloggerName { get; set; }

MaxLength 注釈は、プロパティの長さを 10 に設定することによってデータベースに影響を及ぼします。

Blogs table showing max length on BloggerName column

この検証は、MVC クライアント側の注釈と EF 4.1 サーバー側の注釈の両方で受け付けられて、この場合も、"フィールド AnnotationName は、最大長が '10' の文字列または配列型である必要があります。" というエラー メッセージが動的に作成されます。そのメッセージは少し長くなっています。 多くの注釈を使用すると、ErrorMessage 属性によってエラー メッセージを指定できます。

    [MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
    public string BloggerName { get; set; }

Required 注釈で ErrorMessage を指定することもできます。

Create page with custom error message

 

NotMapped

Code First の規則では、サポートされているデータ型のすべてのプロパティが、データベース内で表現されるよう求められています。 しかしこれが常に、お使いのアプリケーションに当てはまるとは限りません。 たとえば、Blog クラスに、Title フィールドと BlogName フィールドに基づいてコードを作成するプロパティがあるとします。 そのプロパティは動的に作成可能で、格納しておく必要はありません。 この BlogCode プロパティのようにデータベースにマップされないプロパティがあれば、NotMapped 注釈を使用してマークできます。

    [NotMapped]
    public string BlogCode
    {
        get
        {
            return Title.Substring(0, 1) + ":" + BloggerName.Substring(0, 1);
        }
    }

 

ComplexType

一連のクラスにわたるドメイン エンティティを記述した後、それらのクラスをレイヤー化して完全なエンティティを記述することは珍しくありません。 たとえばモデルに、BlogDetails というクラスを追加できます。

    public class BlogDetails
    {
        public DateTime? DateCreated { get; set; }

        [MaxLength(250)]
        public string Description { get; set; }
    }

BlogDetails には、どの型のキー プロパティもないことに注意してください。 ドメイン駆動設計では、BlogDetails は値オブジェクトと呼ばれます。 Entity Framework では、値オブジェクトを複合型と呼びます。  複合型は、それら自体を追跡することができません。

ただし、Blog クラスのプロパティ としての BlogDetails は、Blog オブジェクトの一部として追跡されます。 これを CodeF irst に認識させるには、BlogDetails クラスを ComplexType としてマークする必要があります。

    [ComplexType]
    public class BlogDetails
    {
        public DateTime? DateCreated { get; set; }

        [MaxLength(250)]
        public string Description { get; set; }
    }

これで Blog クラスに、そのブログの BlogDetails を表現するプロパティを追加できます。

        public BlogDetails BlogDetail { get; set; }

データベースでは、BlogDetail プロパティに含まれるプロパティを含め、ブログのすべてのプロパティが Blog テーブルに含まれるようになります。 既定では、それぞれの前に、複合型の "BlogDetail" という名前が付けられます。

Blog table with complex type

ConcurrencyCheck

ConcurrencyCheck 注釈を使用すると、ユーザーがエンティティを編集または削除したときに、データベースでコンカレンシー チェックに使用される 1 つ以上のプロパティにフラグを設定できます。 EF デザイナーを使用してきた場合、これはプロパティの ConcurrencyModeFixed に設定することに相当します。

BloggerNameConcurrencyCheck プロパティに追加すると、どのように機能するかを確認しましょう。

    [ConcurrencyCheck, MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
    public string BloggerName { get; set; }

SaveChanges が呼び出されると、BloggerName フィールドに ConcurrencyCheck 注釈があるために、更新ではそのプロパティの元の値が使用されます。 コマンドは、キー値だけでなく、BloggerName の元の値に対してフィルター処理を行うことで、正しい行を見つけようとします。  データベースに送信される UPDATE コマンドの重要な部分を次に示します。ここでは、PrimaryTrackingKey が 1 で、BloggerName が "Julie" である行が更新されます。後者が、そのブログがデータベースから取得されたときの元の値でした。

    where (([PrimaryTrackingKey] = @4) and ([BloggerName] = @5))
    @4=1,@5=N'Julie'

その間に他のユーザーがそのブログのブロガー名を変更した場合、この更新は失敗し、処理する必要がある DbUpdateConcurrencyException が表示されます。

 

タイムスタンプ

コンカレンシー チェックには、rowversion または timestamp フィールドを使用する方が一般的です。 しかし、プロパティの型がバイト配列である限り、ConcurrencyCheck 注釈を使用するのではなく、より具体的な TimeStamp 注釈を使用できます。 Code First では ConcurrencyCheck プロパティと同様に Timestamp プロパティが処理されますが、Code First によって生成されるデータベース フィールドが null 非許容であることも確認されます。 特定のクラス内に設定できる timestamp プロパティは 1 つだけです。

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

    [Timestamp]
    public Byte[] TimeStamp { get; set; }

Code First の結果として、データベース テーブルに null 非許容の timestamp 列が作成されます。

Blogs table with time stamp column

 

Table と Column

Code First によってデータベースを作成しようとしている場合、作成されるテーブルや列の名前を変更したい場合があります。 既存のデータベースで Code First を使用することもできます。 しかし、ドメイン内のクラスやプロパティの名前が、データベース内のテーブルや列の名前と一致している場合ばかりではありません。

自分のクラスの名前が Blog であると、規則により、Code First では、これが Blogs という名前のテーブルにマップされると推定されます。 そうでない場合は、Table 属性を使用してテーブルの名前を指定できます。 ここではたとえば、注釈によってテーブル名が InternalBlogs であることが指定されています。

    [Table("InternalBlogs")]
    public class Blog

Column 注釈は、マップされる列の属性をより指定しやすい方法です。 名前やデータ型に加えて、テーブル内に列が出現する順序さえ指定できます。 次に、Column 属性の例を示します。

    [Column("BlogDescription", TypeName="ntext")]
    public String Description {get;set;}

Column の TypeName 属性と、DataType による DataAnnotation を混同しないでください。 DataType は UI のために使用される注釈であり、Code First では無視されます。

ここで、再生成後のテーブルを示します。 テーブル名は InternalBlogs に変更されていて、複合型の Description 列が BlogDescription になっています。 注釈で名前が指定されたため、Code First では、列名を複合型の名前で開始する規則は使用されません。

Blogs table and column renamed

 

DatabaseGenerated

重要なデータベース機能の 1 つは、計算されたプロパティを持つ機能です。 計算される列が含まれるテーブルに Code First クラスをマップしようとしている場合、Entity Framework では、それらの列の更新を試みる必要はないと思われます。 しかし、データの挿入または更新後、EF によってこれらの値をデータベースから返したいとします。 DatabaseGenerated 注釈を使用して、Computed 列挙型と共に、クラス内のそれらのプロパティにフラグを設定できます。 その他の列挙型は NoneIdentity です。

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime DateCreated { get; set; }

Code First でデータベースを生成するときには、生成されるデータベースを、byte または timestamp の列で使用できます。その他の場合は、Code First で計算される列の数式を特定できないため、既存のデータベースを指し示している場合にのみこれを使用する必要があります。

上の説明は、既定では、データベース内では整数であるキー プロパティが ID キーになると読めます。 これは、DatabaseGeneratedDatabaseGeneratedOption.Identity に設定した場合と同じです。 それを ID キーにしたくない場合は、値を DatabaseGeneratedOption.None に設定できます。

 

インデックス

Note

EF6.1 以降のみ - Index 属性は、Entity Framework 6.1 で導入されました。 以前のバージョンを使おうとしている場合、このセクションの情報は当てはまりません。

IndexAttribute を使用して、1 つ以上の列にインデックスを作成できます。 属性を 1 つ以上のプロパティに追加すると、EF では、対応するインデックスがデータベースの作成時にデータベース内に作成されるか、Code First Migrations を使おうとしている場合は対応する CreateIndex 呼び出しがスキャフォールディングされます。

たとえば次のコードでは、データベース内の Posts テーブルの Rating 列にインデックスが作成される結果となります。

    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        [Index]
        public int Rating { get; set; }
        public int BlogId { get; set; }
    }

既定では、インデックスの名前は IX_<プロパティ名> (上の例では IX_Rating) になります。 ただし、インデックスの名前を指定することもできます。 次の例では、インデックスの名前を PostRatingIndex にする必要があることを指定しています。

    [Index("PostRatingIndex")]
    public int Rating { get; set; }

インデックスは、既定では一意ではありませんが、名前付きパラメーターの IsUnique を使用すると、一意のインデックスにする必要があることを指定できます。 次の例では、User のログイン名に一意のインデックスを付けています。

    public class User
    {
        public int UserId { get; set; }

        [Index(IsUnique = true)]
        [StringLength(200)]
        public string Username { get; set; }

        public string DisplayName { get; set; }
    }

複数列のインデックス

複数の列にまたがるインデックスは、特定のテーブルで複数の Index 注釈に同じ名前を使用することで指定します。 複数列のインデックスを作成するときには、インデックス内での列の順序を指定する必要があります。 たとえば次のコードでは、RatingBlogId に対して、IX_BlogIdAndRating という名前の複数列のインデックスを作成しています。 BlogId がインデックスの最初の列で、Rating が 2 番目の列です。

    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        [Index("IX_BlogIdAndRating", 2)]
        public int Rating { get; set; }
        [Index("IX_BlogIdAndRating", 1)]
        public int BlogId { get; set; }
    }

 

リレーションシップ属性: InverseProperty と ForeignKey

Note

このページでは、Code First モデル内に、データ注釈を使用してリレーションシップを設定することについて説明しています。 EF でのリレーションシップの一般的な情報と、リレーションシップを使用してデータのアクセスと操作を行う方法については、「リレーションシップとナビゲーション プロパティ」を参照してください。*

Code First の規則によって、モデル内の最も一般的なリレーションシップが処理されますが、助けが必要な場合があります。

Blog クラスのキー プロパティの名前を変更すると、Post とのリレーションシップに問題が発生しました。 

データベースの生成時には、クラス名に一致する規則に加え、Blog クラスの外部キーとしての Id によって、Code First で Post クラスの BlogId プロパティが確認され、認識されます。 しかし、Blog クラスに BlogId プロパティはありません。 これを解決するには、Post 内にナビゲーション プロパティを作成し、ForeignKey DataAnnotation を使用して、Code First で (Post.BlogId プロパティを使用して) 2 つのクラス間にリレーションシップを構築する方法と、データベース内に制約を指定する方法を認識できるようにします。

    public class Post
    {
            public int Id { get; set; }
            public string Title { get; set; }
            public DateTime DateCreated { get; set; }
            public string Content { get; set; }
            public int BlogId { get; set; }
            [ForeignKey("BlogId")]
            public Blog Blog { get; set; }
            public ICollection<Comment> Comments { get; set; }
    }

データベース内の制約に、InternalBlogs.PrimaryTrackingKeyPosts.BlogId の間のリレーションシップが示されます。 

relationship between InternalBlogs.PrimaryTrackingKey and Posts.BlogId

InverseProperty は、クラス間に複数のリレーションシップがある場合に使用されます。

Post クラスでは、ブログ投稿を書いたユーザーと、それを編集したユーザーを追跡できます。 次に、Post クラスの 2 つの新しいナビゲーション プロパティを示します。

    public Person CreatedBy { get; set; }
    public Person UpdatedBy { get; set; }

これらのプロパティによって参照される Person クラスでの追加を行う必要もあります。 Person クラスには、Post に戻るナビゲーション プロパティがあります。1 つは特定のユーザーによって書き込まれたすべての投稿を、もう 1 つはそのユーザーによって更新されたすべての投稿を表します。

    public class Person
    {
            public int Id { get; set; }
            public string Name { get; set; }
            public List<Post> PostsWritten { get; set; }
            public List<Post> PostsUpdated { get; set; }
    }

Code First では、2 つのクラスのプロパティを独力で照合することができません。 Posts 用のデータベース テーブルでは、ユーザー CreatedBy に対して 1 つの外部キー、ユーザー UpdatedBy に対してもう 1 つの外部キーが必要ですが、Code First では、Person_IdPerson_Id1CreatedBy_IdUpdatedBy_Id という 4 つの外部キー プロパティが作成されます。

Posts table with extra foreign keys

この問題を解決するには、InverseProperty 注釈を使用してプロパティの配置を指定します。

    [InverseProperty("CreatedBy")]
    public List<Post> PostsWritten { get; set; }

    [InverseProperty("UpdatedBy")]
    public List<Post> PostsUpdated { get; set; }

Person 内の PostsWritten プロパティでは、これが Post 型を参照することが認識されているため、Post.CreatedBy へのリレーションシップが構築されます。 同様に、PostsUpdatedPost.UpdatedBy に結び付けられます。 こうして、Code First では余分な外部キーが作成されません。

Posts table without extra foreign keys

 

まとめ

DataAnnotations を使用すると、Code First クラス内にクライアントおよびサーバー側の検証を記述できるだけでなく、クラスに関して、規則に基づいて Code First によって想定されることを強化したり、それを修正したりすることさえできます。 DataAnnotations の利用時には、データベース スキーマの生成を開始できるだけでなく、Code First クラスを既存のデータベースにマップすることもできます。

DataAnnotations は非常に柔軟性に富んでいますが、DataAnnotations では、Code First クラスに対して加えることができる、よく必要とされる構成変更だけが提供されています。 一部の極端なケースのためにクラスを構成する場合は、別の構成メカニズムである Code First の Fluent API を検討する必要があります。