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 として定義されます。
複合キー
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 アプリケーションによってクライアント側の検証が実行され、メッセージの動的作成さえも、プロパティと注釈の名前を使用して行われます。
Required 属性は、マップされたプロパティを null 非許容にすると、生成されるデータベースにも影響するようになります。 [タイトル] フィールドが、"not null" に変化したことに注目してください。
Note
ときには、プロパティが必要であっても、データベース内の列を null 非許容にすることができない場合があります。 たとえば、TPH 継承戦略の使用時には、複数の型のデータが 1 つのテーブルに格納されます。 派生型に必須プロパティが含まれる場合、階層内のすべての型がこのプロパティを持つわけではないため、列を null 非許容にすることができません。
MaxLength と MinLength
MaxLength
属性と MinLength
属性を使用すると、Required
を使用して行ったのと同様に、追加のプロパティの検証を指定できます。
次に示すのは、要件 Length がある BloggerName です。 この例は、属性を結合する方法も示しています。
[MaxLength(10),MinLength(5)]
public string BloggerName { get; set; }
MaxLength 注釈は、プロパティの長さを 10 に設定することによってデータベースに影響を及ぼします。
この検証は、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 を指定することもできます。
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" という名前が付けられます。
ConcurrencyCheck
ConcurrencyCheck
注釈を使用すると、ユーザーがエンティティを編集または削除したときに、データベースでコンカレンシー チェックに使用される 1 つ以上のプロパティにフラグを設定できます。 EF デザイナーを使用してきた場合、これはプロパティの ConcurrencyMode
を Fixed
に設定することに相当します。
BloggerName
を ConcurrencyCheck
プロパティに追加すると、どのように機能するかを確認しましょう。
[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 列が作成されます。
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 では、列名を複合型の名前で開始する規則は使用されません。
DatabaseGenerated
重要なデータベース機能の 1 つは、計算されたプロパティを持つ機能です。 計算される列が含まれるテーブルに Code First クラスをマップしようとしている場合、Entity Framework では、それらの列の更新を試みる必要はないと思われます。 しかし、データの挿入または更新後、EF によってこれらの値をデータベースから返したいとします。 DatabaseGenerated
注釈を使用して、Computed
列挙型と共に、クラス内のそれらのプロパティにフラグを設定できます。 その他の列挙型は None
と Identity
です。
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateCreated { get; set; }
Code First でデータベースを生成するときには、生成されるデータベースを、byte または timestamp の列で使用できます。その他の場合は、Code First で計算される列の数式を特定できないため、既存のデータベースを指し示している場合にのみこれを使用する必要があります。
上の説明は、既定では、データベース内では整数であるキー プロパティが ID キーになると読めます。 これは、DatabaseGenerated
を DatabaseGeneratedOption.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 注釈に同じ名前を使用することで指定します。 複数列のインデックスを作成するときには、インデックス内での列の順序を指定する必要があります。 たとえば次のコードでは、Rating
と BlogId
に対して、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.PrimaryTrackingKey
と 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_Id、Person_Id1、CreatedBy_Id、UpdatedBy_Id という 4 つの外部キー プロパティが作成されます。
この問題を解決するには、InverseProperty
注釈を使用してプロパティの配置を指定します。
[InverseProperty("CreatedBy")]
public List<Post> PostsWritten { get; set; }
[InverseProperty("UpdatedBy")]
public List<Post> PostsUpdated { get; set; }
Person 内の PostsWritten
プロパティでは、これが Post
型を参照することが認識されているため、Post.CreatedBy
へのリレーションシップが構築されます。 同様に、PostsUpdated
は Post.UpdatedBy
に結び付けられます。 こうして、Code First では余分な外部キーが作成されません。
まとめ
DataAnnotations を使用すると、Code First クラス内にクライアントおよびサーバー側の検証を記述できるだけでなく、クラスに関して、規則に基づいて Code First によって想定されることを強化したり、それを修正したりすることさえできます。 DataAnnotations の利用時には、データベース スキーマの生成を開始できるだけでなく、Code First クラスを既存のデータベースにマップすることもできます。
DataAnnotations は非常に柔軟性に富んでいますが、DataAnnotations では、Code First クラスに対して加えることができる、よく必要とされる構成変更だけが提供されています。 一部の極端なケースのためにクラスを構成する場合は、別の構成メカニズムである Code First の Fluent API を検討する必要があります。
.NET