エンティティフレームワーク-データ注釈

DataAnnotationsは、最も一般的に必要な構成を強調するクラスを構成するために使用されます。DataAnnotationsは、ASP.NET MVCなどの多くの.NETアプリケーションでも理解されます。これにより、これらのアプリケーションは、クライアント側の検証に同じ注釈を利用できます。DataAnnotation属性は、デフォルトのCodeFirst規則をオーバーライドします。

System.ComponentModel.DataAnnotations 列のnull可能性またはサイズに影響を与える次の属性が含まれます。

  • Key
  • Timestamp
  • ConcurrencyCheck
  • Required
  • MinLength
  • MaxLength
  • StringLength

System.ComponentModel.DataAnnotations.Schema 名前空間には、データベースのスキーマに影響を与える次の属性が含まれています。

  • Table
  • Column
  • Index
  • ForeignKey
  • NotMapped
  • InverseProperty

キー

Entity Frameworkは、エンティティの追跡に使用するキー値を持つすべてのエンティティに依存しています。Code Firstが依存する規則の1つは、どのプロパティが各CodeFirstクラスのキーであるかをどのように意味するかです。

  • 慣例では、「Id」という名前のプロパティ、またはクラス名と「Id」を組み合わせたプロパティ(「StudentId」など)を探します。

  • プロパティは、データベースの主キー列にマップされます。

  • 学生、コース、および登録クラスは、この規則に従います。

ここで、StudentクラスがIDではなくStdntIDという名前を使用したとしましょう。Code Firstがこの規則に一致するプロパティを見つけられない場合、キープロパティが必要であるというEntity Frameworkの要件のため、例外がスローされます。キー注釈を使用して、EntityKeyとして使用するプロパティを指定できます。

StdntIDを含むStudentクラスの次のコードを見てみましょう。ただし、デフォルトのCodeFirst規則に従っていません。したがって、これを処理するために、キー属性が追加され、主キーになります。

public class Student {

   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

アプリケーションを実行し、SQL Server Explorerでデータベースを調べると、主キーがStudentsテーブルのStdntIDになっていることがわかります。

Entity Frameworkは、複合キーもサポートしています。 Composite keys複数のプロパティで構成される主キーでもあります。たとえば、主キーがLicenseNumberとIssuingCountryの組み合わせであるDrivingLicenseクラスがあるとします。

public class DrivingLicense {

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

複合キーがある場合、Entity Frameworkでは、キープロパティの順序を定義する必要があります。これは、列アノテーションを使用して順序を指定することで実行できます。

タイムスタンプ

Code Firstは、TimestampプロパティをConcurrencyCheckプロパティと同じように扱いますが、コードが最初に生成するデータベースフィールドがnull不可であることも保証します。

  • 並行性チェックには、rowversionフィールドまたはtimestampフィールドを使用するのが一般的です。

  • プロパティのタイプがバイト配列である限り、ConcurrencyCheckアノテーションを使用する代わりに、より具体的なTimeStampアノテーションを使用できます。

  • 特定のクラスに含めることができるタイムスタンププロパティは1つだけです。

TimeStampプロパティをCourseクラスに追加して、簡単な例を見てみましょう。

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   public int Credits { get; set; }
   [Timestamp]
   public byte[] TStamp { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

上記の例でわかるように、Timestamp属性はCourseクラスのByte []プロパティに適用されます。したがって、TStampCodeFirstはCoursesテーブルにタイムスタンプ列を作成します。

ConcurrencyCheck

ConcurrencyCheckアノテーションを使用すると、ユーザーがエンティティを編集または削除したときに、データベースで同時実行性チェックに使用される1つ以上のプロパティにフラグを立てることができます。EFデザイナを使用している場合、これはプロパティのConcurrencyModeをFixedに設定することと一致します。

ConcurrencyCheckをCourseクラスのTitleプロパティに追加することにより、ConcurrencyCheckがどのように機能するかの簡単な例を見てみましょう。

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   public string Title { get; set; }
   public int Credits { get; set; }
   [Timestamp, DataType("timestamp")]
   public byte[] TimeStamp { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

上記のCourseクラスでは、ConcurrencyCheck属性が既存のTitleプロパティに適用されます。これで、Code Firstは、次のコードに示すように、楽観的同時実行性をチェックするためにupdateコマンドにTitle列を含めます。

exec sp_executesql N'UPDATE [dbo].[Courses]
   SET [Title] = @0
   WHERE (([CourseID] = @1) AND ([Title] = @2))
   ',N'@0 nvarchar(max) ,@1 int,@2 nvarchar(max) ',@0=N'Maths',@1=1,@2=N'Calculus'
go

必要な注釈

Requiredアノテーションは、特定のプロパティが必要であることをEFに通知します。必須IDがFirstMidNameプロパティに追加されている次のStudentクラスを見てみましょう。必須属性は、プロパティにデータが含まれていることをEFに強制します。

public class Student {

   [Key]
   public int StdntID { get; set; }

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

   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

上記の例に見られるように、Required属性はFirstMidNameとLastNameに適用されます。したがって、Code Firstは、次の図に示すように、StudentsテーブルにNOT NULLFirstMidName列とLastName列を作成します。

MaxLength

MaxLength属性を使用すると、追加のプロパティ検証を指定できます。これは、ドメインクラスの文字列または配列型のプロパティに適用できます。EF Code Firstは、MaxLength属性で指定された列のサイズを設定します。

MaxLength(24)属性がTitleプロパティに適用されている次のCourseクラスを見てみましょう。

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

上記のアプリケーションを実行すると、次の図に示すように、Code FirstはCourseIdテーブルにnvarchar(24)列のタイトルを作成します。

ユーザーが24文字を超えるタイトルを設定すると、EFはEntityValidationErrorをスローします。

MinLength

MinLength属性を使用すると、MaxLengthの場合と同様に、追加のプロパティ検証を指定することもできます。次のコードに示すように、MinLength属性はMaxLength属性と一緒に使用することもできます。

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24) , MinLength(5)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

MinLength属性で指定された長さよりも小さい、またはMaxLength属性で指定された長さよりも大きいTitleプロパティの値を設定すると、EFはEntityValidationErrorをスローします。

StringLength

StringLengthを使用すると、MaxLengthなどの追加のプロパティ検証を指定することもできます。唯一の違いは、StringLength属性はドメインクラスの文字列型プロパティにのみ適用できることです。

public class Course {

   public int CourseID { get; set; }
   [StringLength (24)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Entity Frameworkは、StringLength属性のプロパティの値も検証します。ユーザーが24文字を超えるタイトルを設定すると、EFはEntityValidationErrorをスローします。

テーブル

デフォルトのCodeFirst規則では、クラス名と同様のテーブル名が作成されます。Code Firstにデータベースを作成させ、作成するテーブルの名前も変更したい場合。次に−

  • 既存のデータベースでCodeFirstを使用できます。ただし、クラスの名前がデータベース内のテーブルの名前と一致するとは限りません。

  • テーブル属性は、このデフォルトの規則をオーバーライドします。

  • EF Code Firstは、指定されたドメインクラスのTable属性に指定された名前のテーブルを作成します。

クラスの名前がStudentである次の例を見てみましょう。慣例により、Code Firstは、これがStudentsという名前のテーブルにマップされると想定しています。そうでない場合は、次のコードに示すように、Table属性を使用してテーブルの名前を指定できます。

[Table("StudentsInfo")]
public class Student {

   [Key]
   public int StdntID { get; set; }
   [Required]
   public string LastName { get; set; }
   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

これで、Table属性がテーブルをStudentsInfoとして指定していることがわかります。テーブルが生成されると、次の画像に示すように、テーブル名StudentsInfoが表示されます。

次のコードに示すように、テーブル名を指定できるだけでなく、Table属性を使用してテーブルのスキーマを指定することもできます。

[Table("StudentsInfo", Schema = "Admin")] 
public class Student {

   [Key]
   public int StdntID { get; set; }
   [Required]
   public string LastName { get; set; }
   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

上記の例でわかるように、テーブルは管理スキーマで指定されています。次の図に示すように、CodeFirstはAdminスキーマにStudentsInfoテーブルを作成します。

カラム

これもTable属性と同じですが、Table属性はテーブルの動作をオーバーライドし、Column属性は列の動作をオーバーライドします。デフォルトのコードファースト規則では、プロパティ名と同様の列名が作成されます。Code Firstにデータベースを作成させ、テーブルの列の名前も変更したい場合。次に−

  • 列属性はデフォルトの規則をオーバーライドします。

  • EF Code Firstは、指定されたプロパティのColumn属性に指定された名前の列を作成します。

プロパティの名前がFirstMidNameである次の例を見てみましょう。慣例により、Code Firstは、これがFirstMidNameという名前の列にマップされると想定しています。

そうでない場合は、次のコードに示すように、Column属性を使用して列の名前を指定できます。

public class Student {

   public int ID { get; set; }
   public string LastName { get; set; }
   [Column("FirstName")]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Column属性が列をFirstNameとして指定していることがわかります。テーブルが生成されると、次の図に示すように、列名FirstNameが表示されます。

インデックス

Index属性は、Entity Framework6.1で導入されました。以前のバージョンを使用している場合、このセクションの情報は適用されません。

  • IndexAttributeを使用して、1つ以上の列にインデックスを作成できます。

  • 1つ以上のプロパティに属性を追加すると、EFはデータベースの作成時に、データベースに対応するインデックスを作成します。

  • ほとんどの場合、インデックスを使用すると、データの取得がより高速かつ効率的になります。ただし、テーブルまたはビューをインデックスでオーバーロードすると、挿入や更新などの他の操作のパフォーマンスに不快な影響を与える可能性があります。

  • インデックス作成はEntityFrameworkの新機能であり、データベースからデータをクエリするために必要な時間を短縮することで、CodeFirstアプリケーションのパフォーマンスを向上させることができます。

  • Index属性を使用してデータベースにインデックスを追加し、デフォルトの一意の設定とクラスター化された設定をオーバーライドして、シナリオに最適なインデックスを取得できます。

  • デフォルトでは、インデックスの名前はIX_ <プロパティ名>になります。

クレジットのコースクラスにIndex属性が追加されている次のコードを見てみましょう。

public class Course {
   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Index属性がCreditsプロパティに適用されていることがわかります。テーブルが生成されると、インデックスにIX_Creditsが表示されます。

デフォルトでは、インデックスは一意ではありませんが、 IsUniqueインデックスが一意であることを指定する名前付きパラメータ。次の例では、次のコードに示すような一意のインデックスを導入しています。

public class Course {
   public int CourseID { get; set; }
   [Index(IsUnique = true)]
	
   public string Title { get; set; }
   [Index]
	
   public int Credits { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

外部キー

コードファースト規則は、モデル内の最も一般的な関係を処理しますが、助けが必要な場合があります。たとえば、Studentクラスのキープロパティの名前を変更すると、Enrollmentクラスとの関係に問題が発生しました。

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course Course { get; set; }
   public virtual Student Student { get; set; }
}

public class Student {
   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

データベースの生成中に、Code FirstはEnrollmentクラスのStudentIDプロパティを確認し、クラス名と「ID」を組み合わせたものと一致するという規則により、Studentクラスの外部キーとして認識します。ただし、StudentクラスにはStudentIDプロパティはありませんが、StdntIDプロパティはStudentクラスです。

これに対する解決策は、Enrollmentにナビゲーションプロパティを作成し、ForeignKey DataAnnotationを使用して、次のコードに示すように、CodeFirstが2つのクラス間の関係を構築する方法を理解できるようにすることです。

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
	
   public Grade? Grade { get; set; }
   public virtual Course Course { get; set; }
   [ForeignKey("StudentID")]
	
   public virtual Student Student { get; set; }
}

これで、ForeignKey属性がナビゲーションプロパティに適用されていることがわかります。

NotMapped

コードファーストのデフォルトの規則では、サポートされているデータ型であり、ゲッターとセッターを含むすべてのプロパティがデータベースに表示されます。ただし、これはアプリケーションでは常に当てはまるとは限りません。NotMapped属性は、このデフォルトの規則をオーバーライドします。たとえば、FatherNameなどのStudentクラスにプロパティがある場合、それを保存する必要はありません。次のコードに示すように、データベースに列を作成したくないFatherNameプロパティにNotMapped属性を適用できます。

public class Student {
   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
	
   public DateTime EnrollmentDate { get; set; }
   [NotMapped]

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

NotMapped属性がFatherNameプロパティに適用されていることがわかります。テーブルが生成されると、FatherName列はデータベースに作成されませんが、Studentクラスには存在することがわかります。

次のStudentクラスのAddressプロパティとAgeプロパティの例に示すように、Code Firstは、ゲッターもセッターも持たないプロパティの列を作成しません。

InverseProperty

InversePropertyは、クラス間に複数の関係がある場合に使用されます。登録クラスでは、現在のコースと前のコースを誰が登録したかを追跡することができます。Enrollmentクラスに2つのナビゲーションプロパティを追加しましょう。

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course CurrCourse { get; set; }
   public virtual Course PrevCourse { get; set; }
   public virtual Student Student { get; set; }
}

同様に、これらのプロパティによって参照されるコースクラスを追加する必要もあります。Courseクラスには、現在および以前のすべての登録を含むEnrollmentクラスに戻るナビゲーションプロパティがあります。

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]

   public int Credits { get; set; }
   public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
   public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}

上記のクラスに示されているように、外部キープロパティが特定のクラスに含まれていない場合、コードは最初に{Class Name} _ {PrimaryKey}外部キー列を作成します。データベースが生成されると、次の外部キーが表示されます。

ご覧のとおり、Codeは最初に、2つのクラスのプロパティを単独で一致させることはできません。Enrollmentsのデータベーステーブルには、CurrCourse用に1つ、PrevCourse用に1つの外部キーが必要ですが、CodeFirstは4つの外部キープロパティを作成します。

  • CurrCourse _CourseID
  • PrevCourse _CourseID
  • Course_CourseID、および
  • Course_CourseID1

これらの問題を修正するには、InversePropertyアノテーションを使用して、プロパティの配置を指定できます。

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]

   public int Credits { get; set; }
   [InverseProperty("CurrCourse")]

   public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
   [InverseProperty("PrevCourse")]

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

ご覧のとおり、InverseProperty属性は、それが属するEnrollmentクラスの参照プロパティを指定することにより、上記のCourseクラスに適用されます。これで、Code Firstはデータベースを生成し、次の図に示すように、登録テーブルに2つの外部キー列のみを作成します。

理解を深めるために、上記の例を段階的に実行することをお勧めします。