實體框架 - 資料註解



DataAnnotations 用於配置類,這些類將突出顯示最常用的配置。DataAnnotations 也被許多 .NET 應用程式(如 ASP.NET MVC)理解,這允許這些應用程式利用相同的註解進行客戶端驗證。DataAnnotation 屬性會覆蓋預設的 CodeFirst 約定。

System.ComponentModel.DataAnnotations 包括以下影響列的可空性或大小的屬性。

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

System.ComponentModel.DataAnnotations.Schema 名稱空間包含以下影響資料庫模式的屬性。

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

Key

實體框架依賴於每個實體都具有一個鍵值,它用於跟蹤實體。Code First 依賴的約定之一是如何暗示每個 Code First 類中的哪個屬性是鍵。

  • 約定是查詢名為“Id”的屬性或將類名和“Id”組合在一起的屬性,例如“StudentId”。

  • 該屬性將對映到資料庫中的主鍵列。

  • Student、Course 和 Enrollment 類遵循此約定。

現在假設 Student 類使用 StdntID 而不是 ID。當 Code First 找不到與該約定匹配的屬性時,由於實體框架要求您必須具有鍵屬性,因此它將丟擲異常。您可以使用 key 註解來指定哪個屬性用作 EntityKey。

讓我們看一下包含 StdntID 的 Student 類的以下程式碼,但它不遵循預設的 Code First 約定。因此,為了處理這個問題,添加了一個 Key 屬性,這將使其成為主鍵。

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 資源管理器中檢視資料庫時,您將看到主鍵現在是 Students 表中的 StdntID。

Primary Key

實體框架也支援複合鍵。複合鍵也是主鍵,由多個屬性組成。例如,您有一個 DrivingLicense 類,其主鍵是 LicenseNumber 和 IssuingCountry 的組合。

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; }
}

當您有複合鍵時,實體框架要求您定義鍵屬性的順序。您可以使用 Column 註解來指定順序。

Column Annotation

Timestamp

Code First 將以與 ConcurrencyCheck 屬性相同的方式處理 Timestamp 屬性,但它還將確保 Code First 生成的資料庫欄位不可為空。

  • 更常見的是使用 rowversion 或 timestamp 欄位進行併發檢查。

  • 您可以使用更具體的 TimeStamp 註解而不是使用 ConcurrencyCheck 註解,只要屬性的型別是位元組陣列即可。

  • 您在一個給定類中只能有一個 timestamp 屬性。

讓我們看一個簡單的例子,將 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[] 屬性。因此,Code First 將在 Courses 表中建立一個名為 TStamp 的 timestamp 列。

ConcurrencyCheck

ConcurrencyCheck 註解允許您標記一個或多個屬性,以便在使用者編輯或刪除實體時在資料庫中用於併發檢查。如果您一直在使用 EF 設計器,這與將屬性的 ConcurrencyMode 設定為 Fixed 相一致。

讓我們看一個 ConcurrencyCheck 如何工作的簡單示例,將其新增到 Course 類中的 Title 屬性中。

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 將在更新命令中包含 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 註解

Required 註解告訴 EF 特定屬性是必需的。讓我們看一下以下 Student 類,其中 Required id 新增到 FirstMidName 屬性中。Required 屬性將強制 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 NULL FirstMidName 和 LastName 列,如下面的影像所示。

Not Null

MaxLength

MaxLength 屬性允許您指定其他屬性驗證。它可以應用於域類的字串或陣列型別屬性。EF Code First 將根據 MaxLength 屬性中指定的大小設定列的大小。

讓我們看一下以下 Course 類,其中 MaxLength(24) 屬性應用於 Title 屬性。

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 表中建立一個名為 Title 的 nvarchar(24) 列,如下面的影像所示。

nvarchar Column

當用戶設定包含超過 24 個字元的 Title 時,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; }
}

如果將 Title 屬性的值設定為小於 MinLength 屬性中指定長度或大於 MaxLength 屬性中指定長度的值,則 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; }
}

實體框架還會驗證 StringLength 屬性的屬性值。如果使用者設定包含超過 24 個字元的 Title,則 EF 將丟擲 EntityValidationError。

Table

預設的 Code First 約定建立與類名類似的表名。如果您讓 Code First 建立資料庫,並且還希望更改它正在建立的表名。然後 -

  • 您可以將 Code First 與現有資料庫一起使用。但並非總是類名與資料庫中表名匹配的情況。

  • Table 屬性覆蓋此預設約定。

  • 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,如下面的影像所示。

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; }
}

您可以在上面的示例中看到,表使用 admin 架構指定。現在 Code First 將在 Admin 架構中建立 StudentsInfo 表,如下面的影像所示。

Admin Schema

Column

它也與 Table 屬性相同,但 Table 屬性覆蓋錶行為,而 Column 屬性覆蓋列行為。預設的 Code First 約定建立與屬性名類似的列名。如果您讓 Code First 建立資料庫,並且還希望更改表中的列名。然後 -

  • Column 屬性覆蓋預設約定。

  • 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,如下面的影像所示。

FirstName

Index

Index 屬性是在 Entity Framework 6.1 中引入的。如果您使用的是早期版本,則本節中的資訊不適用。

  • 您可以使用 IndexAttribute 在一個或多個列上建立索引。

  • 將屬性新增到一個或多個屬性將導致 EF 在建立資料庫時在資料庫中建立相應的索引。

  • 在大多數情況下,索引可以使資料檢索更快更高效。但是,在表或檢視上使用過多的索引可能會對其他操作(如插入或更新)的效能產生負面影響。

  • 索引是 Entity Framework 中的新功能,您可以透過減少從資料庫查詢資料所需的時間來提高 Code First 應用程式的效能。

  • 您可以使用 Index 屬性向資料庫新增索引,並覆蓋預設的 Unique 和 Clustered 設定,以獲得最適合您方案的索引。

  • 預設情況下,索引將命名為 IX_<屬性名稱>

讓我們看一下下面的程式碼,其中在 Course 類中為 Credits 添加了 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。

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; }
}

ForeignKey

Code First 約定將處理模型中最常見的關聯關係,但某些情況下需要幫助。例如,更改 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 資料註釋來幫助 Code First 理解如何在兩個類之間構建關係,如下面的程式碼所示。

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 屬性已應用於導航屬性。

ForeignKey Attribute

NotMapped

根據 Code First 的預設約定,每個支援的資料型別的屬性,並且包含 getter 和 setter 的屬性都將在資料庫中表示。但這並非始終適用於您的應用程式。NotMapped 屬性會覆蓋此預設約定。例如,您可能在 Student 類中有一個名為 FatherName 的屬性,但不需要儲存它。您可以將 NotMapped 屬性應用於 FatherName 屬性,您不希望在資料庫中建立該屬性的列,如下面的程式碼所示。

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 類中。

NotMapped Attribute

對於沒有 getter 或 setter 的屬性,Code First 不會建立列,如下面的 Student 類 Address 和 Age 屬性示例所示。

InverseProperty

當您在類之間有多個關係時,使用 InverseProperty。在 Enrollment 類中,您可能希望跟蹤誰註冊了 Current Course 和 Previous Course。讓我們為 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 CurrCourse { get; set; }
   public virtual Course PrevCourse { get; set; }
   public virtual Student Student { get; set; }
}

同樣,您還需要在這些屬性引用的 Course 類中新增。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; }
}

如果外部索引鍵屬性未包含在特定類中,則 Code First 會建立 {類名} _{主鍵} 外部索引鍵列,如上述類所示。生成資料庫時,您將看到以下外部索引鍵。

Foreign Keys

如您所見,Code First 無法自行匹配兩個類中的屬性。Enrollments 的資料庫表應該有一個用於 CurrCourse 的外部索引鍵和一個用於 PrevCourse 的外部索引鍵,但 Code First 將建立四個外部索引鍵屬性,即

  • 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; }
}

如您所見,透過指定它屬於 Enrollment 類的哪個引用屬性,在上面的 Course 類中應用了 InverseProperty 屬性。現在,Code First 將生成一個數據庫並在 Enrollments 表中僅建立兩個外部索引鍵列,如下面的影像所示。

Foreign Key Columns

我們建議您逐步執行以上示例,以便更好地理解。

廣告