ASP.NET MVC - 資料註解



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

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

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

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

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

Key

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

約定是查詢名為“Id”的屬性,或將類名和“Id”組合在一起的屬性,例如“StudentId”。該屬性將對映到資料庫中的主鍵列。“Student”、“Course”和“Enrollment”類遵循此約定。

現在假設 Student 類使用 StdntID 而不是 ID。當 Code First 找不到與該約定匹配的屬性時,由於 Entity Framework 要求您必須具有鍵屬性,因此它將引發異常。

可以使用 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 StdntID

Entity Framework 也支援複合鍵。複合鍵是由多個屬性組成的主鍵。例如,您有一個 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; }
}

當您有複合鍵時,Entity Framework 要求您定義鍵屬性的順序。可以使用 Column 註解指定順序。

Composite Keys

Timestamp

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

更常見的是使用 rowversion 或 timestamp 欄位進行併發檢查。但是,與其使用 ConcurrencyCheck 註解,只要屬性的型別是位元組陣列,就可以使用更具體的 TimeStamp 註解。在一個給定的類中只能有一個 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 表中建立一個 timestamp 列 TStamp。

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

您可以在上面的 Student 類示例中看到 Required 屬性應用於 FirstMidName 和 LastName。因此,Code First 將在 Students 表中建立一個 NOT NULL FirstMidName 和 LastName 列,如下面的螢幕截圖所示。

Students Table

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 將在 Coursed 表中建立一個 nvarchar(24) 列 Title,如下面的螢幕截圖所示。

Column Title Coursed Table

現在,當用戶設定包含超過 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; }
}

Entity Framework 還驗證 StringLength 屬性的屬性值。現在,如果使用者設定的 Title 包含超過 24 個字元,則 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,如下面的螢幕截圖所示。

Table Name 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 表,如下面的螢幕截圖所示。

StudentsInfo Table in 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,如下面的螢幕截圖所示。

Column Name FirstName

Index

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

您可以使用 IndexAttribute 在一個或多個列上建立索引。將屬性新增到一個或多個屬性將導致 EF 在建立資料庫時在資料庫中建立相應的索引。

在大多數情況下,索引使資料檢索更快、更高效。但是,使用索引超載表或檢視可能會令人不快地影響其他操作(如插入或更新)的效能。

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

您可以使用 Index 屬性向資料庫新增索引,並覆蓋預設的 Unique 和 Clustered 設定以獲得最適合您方案的索引。預設情況下,索引將命名為 IX_

讓我們看一下下面的程式碼,其中在 Course 類中為 Credits 添加了 Index 屬性。

public class Cours{
   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 屬性。現在,當生成表時,您將在 Indexes 中看到 IX_Credits。

IX_Credits in Indexes

預設情況下,索引是非唯一的,但您可以使用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 屬性,而是 Student 類中的 StdntID 屬性。

解決此問題的辦法是在 Enrollment 中建立一個導航屬性,並使用 ForeignKey DataAnnotation 來幫助 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` 類中。

FatherName Column Created

對於沒有 getter 或 setter 的屬性,Code First 不會建立列。

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 會建立 `{類名}_{主鍵}` 外部索引鍵列,如上述類所示。生成資料庫時,您會看到許多外部索引鍵,如下面的螢幕截圖所示。

Number of ForeignKeys

如您所見,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; }
}

如您現在所見,當在上面的 `Course` 類中應用 `InverseProperty` 屬性並指定它屬於 `Enrollment` 類的哪個引用屬性時,Code First 將生成資料庫並在 `Enrollments` 表中僅建立兩個外部索引鍵列,如下面的螢幕截圖所示。

ForeignKey Enrollments Table

我們建議您執行上述示例以更好地理解。

廣告