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

1. 鍵(Key)

實體框架(Entity Framework或簡稱為EF )依賴於具有用於跟蹤實體的鍵值的每個實體。 Code First依賴的其中一個約定是它如何暗示哪個屬性是每個Code First類中的鍵。

約定是尋找一個名為Id的屬性,或者將類名和Id結合起來的屬性,比如StudentId。 該屬性將對映到資料庫中的主鍵列。學生,課程和入學課程遵循這個約定。

現在讓假設Student類使用名稱StdntID而不是ID。 當Code First找不到符合此約定的屬性時,它將丟擲一個異常,因為Entity Framework要求必須具有一個鍵屬性。

可以使用鍵注釋來指定哪個屬性將被用作EntityKey

下面來看看包含StdntIDStudent類。 它不遵循預設的Code First約定,所以要處理這個問題,新增了Key屬性,使StdntID列成為主鍵。

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

實體框架(Entity Framework)也支援複合鍵。 複合鍵是由多個屬性組成的主鍵。例如,有一個DrivingLicense類,其主鍵是LicenseNumberIssuingCountry的組合。

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注釋來指定順序。

2. 時間戳(Timestamp)

Code First會將Timestamp屬性視為ConcurrencyCheck屬性,但它也將確保Code First生成的資料庫欄位不可空。

使用rowversiontimestamp欄位進行並行檢查更為常見。但是,不要使用ConcurrencyCheck注釋,只要屬性的型別是位元組陣列,就可以使用更具體的TimeStamp注釋。在給定的類中只能有一個時間戳屬性。

下面來看一個簡單的例子,將TimeStamp屬性新增到Course類中。參考以下程式碼 -

public class Course{
   public int CourseID { get; set; }
   public string Title { get; set; }
   public int Credits { get; set; }
   1571001036
   public byte[] TStamp { get; set; }

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

如上例所示,Timestamp屬性應用於Course類的Byte []屬性。 所以,Code First 將在Courses表中建立一個時間戳列TStamp

3. ConcurrencyCheck

ConcurrencyCheck注釋允許在使用者編輯或刪除實體時標記一個或多個要用於資料庫並行檢查的屬性。如果一直在使用EF Designer,那麼這將與將屬性的「並行性模式」設定為「固定」一致。

下面來看看一個簡單的例子,通過將它新增Title屬性到Course類中來了解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

4. Required

Required注釋告訴實體框架(Entity Framework)需要一個特定的屬性。下面來看看Student類,其中必需的id被新增到FirstMidName屬性中。 Required屬性將強制Entity Framework 確保該屬性中包含資料。

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屬性應用於FirstMidNameLastName。 因此,Code First將在Students表中建立一個NOT NULLFirstMidNameLastName列,如下圖所示。

5. MaxLength

MaxLength屬性用於指定其他屬性驗證。它可以應用於類的字串或陣列型別的屬性。 Entity FrameworkCode 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將在Coursed表中建立一個nvarchar(24)列標題,如以下螢幕截圖所示。

現在當使用者設定包含超過24個字元的標題時,Entity Framework將丟擲EntityValidationError異常。

6. 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屬性中將Title屬性的值設定為小於指定的長度或大於MaxLength屬性中的指定長度,則EF將丟擲EntityValidationError異常。

7. StringLength

StringLength還允許指定其他屬性驗證,如MaxLength。 不同的是StringLength屬性只能應用於Domain類的字串型別屬性。參考以下範例程式碼 -

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異常。

8. Table

預設程式碼第一個約定建立一個與類名相同的表名。 如果讓Code First建立資料庫,則還可以更改正在建立的表的名稱。可以讓Code First使用現有的資料庫表。 但並不總是這樣,有時類的名稱與資料庫中表的名稱不能總是相匹配。

Table屬性重寫此預設約定。 對於給定的域類,EF Code First將在Table屬性使用指定的名稱來建立一個表。

下面來看看一個類名為Student的例子,按照慣例,Code First假定這將對映到一個名為Students的表。 如果不是這種情況,可以使用Table屬性指定表的名稱,如以下程式碼所示 指定要建立的表名稱為:StudentInfo -

[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。 生成表時,如下圖所示的表名StudentInfo

不僅可以指定表名,還可以使用以下程式碼使用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表,如以下螢幕截圖所示。

9. Column

Column屬性也與Table屬性相同,但Table屬性覆蓋表行為,而Column屬性覆蓋列行為。 預設程式碼第一個約定建立一個與屬性名相同的列名。

如果讓Code First建立資料庫,並且還希望更改表中列的名稱。Column屬性重寫此預設約定。 EF Code First將在給定類屬性的Column屬性中建立一個具有指定名稱的列。

下面來看看下面的例子,其中屬性名為FirstMidName,按照慣例,Code First假定這將對映到一個名為FirstMidName的列。 如果不是要對映到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,如以下螢幕截圖所示。

10. Index

Index屬性是在Entity Framework 6.1中引入的。

註 - 如果您使用的是早期版本,則本節中的資訊不適用。

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

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

索引是實體框架中的新功能,可以通過減少從資料庫查詢資料所需的時間來提高Code First應用程式的效能。

可以使用Index屬性將索引新增到資料庫,並覆蓋預設的「唯一」和「群集」設定,以獲得最適合您的方案的索引。預設情況下,索引將被命名為IX_<屬性名稱>

讓我們來看看以下程式碼,其中Index屬性被新增到CourseCredits列上。

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屬性。 現在,當表生成時,將在索引中看到名稱為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; }
}

11. ForeignKey

Code First約定將處理模型中最常見的關係。 例如,通過更改Student類中的key屬性名稱,建立了與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屬性應用於導航屬性。

12. NotMapped

Code First的約定在預設情況下,每個屬於受支援資料型別的屬性都包含gettersetter,它們在資料庫中表示。 但是在應用中並不總是這樣。 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類中。

Code First 不會為沒有gettersetter的屬性建立一個列。

13. InverseProperty

在類之間有多個關係時使用InverseProperty。 在Enrollment類中,可能想要跟蹤註冊「當前課程」的人員和註冊「以前課程」的人員。

我們為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會建立{Class Name} _ {Primary Key}外來鍵列。 生成資料庫表時,您將看到許多外來鍵,如以下螢幕截圖所示。

正如所看到的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; }
}

正如上面所看到的那樣,當InverseProperty屬性通過指定它所屬的Enrollment類的哪個參照屬性應用於上述Course類時,Code First將生成資料庫表,並在Enrollments表中建立兩個外來鍵列,如以下螢幕截圖所示。

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