DataAnnotations用于配置模型類(lèi),它將突出顯示最常用的配置。 DataAnnotations也被許多.NET應(yīng)用程序所理解,例如ASP.NET MVC,它允許這些應(yīng)用程序利用相同的注釋來(lái)進(jìn)行客戶端驗(yàn)證。DataAnnotation屬性重寫(xiě)默認(rèn)的Code-First約定。
System.ComponentModel.DataAnnotations包括以下影響列的可空性或大小的屬性。
System.ComponentModel.DataAnnotations.Schema命名空間包括以下影響數(shù)據(jù)庫(kù)模式的屬性。
實(shí)體框架(Entity Framework或簡(jiǎn)稱為EF )依賴于具有用于跟蹤實(shí)體的鍵值的每個(gè)實(shí)體。 Code First依賴的其中一個(gè)約定是它如何暗示哪個(gè)屬性是每個(gè)Code First類(lèi)中的鍵。
約定是尋找一個(gè)名為Id的屬性,或者將類(lèi)名和Id結(jié)合起來(lái)的屬性,比如StudentId。 該屬性將映射到數(shù)據(jù)庫(kù)中的主鍵列。學(xué)生,課程和入學(xué)課程遵循這個(gè)約定。
現(xiàn)在讓假設(shè)Student類(lèi)使用名稱StdntID而不是ID。 當(dāng)Code First找不到符合此約定的屬性時(shí),它將拋出一個(gè)異常,因?yàn)?em>Entity Framework要求必須具有一個(gè)鍵屬性。
可以使用鍵注釋來(lái)指定哪個(gè)屬性將被用作EntityKey。
下面來(lái)看看包含StdntID的Student類(lèi)。 它不遵循默認(rèn)的Code First約定,所以要處理這個(gè)問(wèn)題,添加了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; }
}
在運(yùn)行應(yīng)用程序并查看SQL Server資源管理器中的數(shù)據(jù)庫(kù)時(shí),您將看到現(xiàn)在Students表中的主鍵是:StdntID。
實(shí)體框架(Entity Framework)也支持復(fù)合鍵。 復(fù)合鍵是由多個(gè)屬性組成的主鍵。例如,有一個(gè)DrivingLicense類(lèi),其主鍵是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; }
}
當(dāng)有組合鍵時(shí),實(shí)體框架要求你定義鍵屬性的順序??梢允褂?code>Column注釋來(lái)指定順序。
Code First會(huì)將Timestamp屬性視為ConcurrencyCheck屬性,但它也將確保Code First生成的數(shù)據(jù)庫(kù)字段不可空。
使用rowversion或timestamp字段進(jìn)行并發(fā)檢查更為常見(jiàn)。但是,不要使用ConcurrencyCheck注釋,只要屬性的類(lèi)型是字節(jié)數(shù)組,就可以使用更具體的TimeStamp注釋。在給定的類(lèi)中只能有一個(gè)時(shí)間戳屬性。
下面來(lái)看一個(gè)簡(jiǎn)單的例子,將TimeStamp屬性添加到Course類(lèi)中。參考以下代碼 -
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屬性應(yīng)用于Course類(lèi)的Byte []屬性。 所以,Code First 將在Courses表中創(chuàng)建一個(gè)時(shí)間戳列TStamp。
ConcurrencyCheck注釋允許在用戶編輯或刪除實(shí)體時(shí)標(biāo)記一個(gè)或多個(gè)要用于數(shù)據(jù)庫(kù)并發(fā)檢查的屬性。如果一直在使用EF Designer,那么這將與將屬性的“并發(fā)性模式”設(shè)置為“固定”一致。
下面來(lái)看看一個(gè)簡(jiǎn)單的例子,通過(guò)將它添加Title屬性到Course類(lèi)中來(lái)了解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類(lèi)中,ConcurrencyCheck屬性應(yīng)用于現(xiàn)有的Title屬性。 Code First將在update命令中包含Title列來(lái)檢查以下代碼所示的樂(lè)觀并發(fā)。
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注釋告訴實(shí)體框架(Entity Framework)需要一個(gè)特定的屬性。下面來(lái)看看Student類(lèi),其中必需的id被添加到FirstMidName屬性中。 Required屬性將強(qiáng)制Entity Framework 確保該屬性中包含數(shù)據(jù)。
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類(lèi)的示例中看到Required屬性應(yīng)用于FirstMidName和LastName。 因此,Code First將在Students表中創(chuàng)建一個(gè)NOT NULL的 FirstMidName和LastName列,如下圖所示。
MaxLength屬性用于指定其他屬性驗(yàn)證。它可以應(yīng)用于類(lèi)的字符串或數(shù)組類(lèi)型的屬性。 Entity Framework的 Code First 將設(shè)置MaxLength屬性中指定的列的大小。
下面來(lái)看看MaxLength(24)屬性應(yīng)用于Title屬性的以下Course類(lèi)。
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; }
}
當(dāng)運(yùn)行上述應(yīng)用程序時(shí),Code-First將在Coursed表中創(chuàng)建一個(gè)nvarchar(24)列標(biāo)題,如以下屏幕截圖所示。
現(xiàn)在當(dāng)用戶設(shè)置包含超過(guò)24個(gè)字符的標(biāo)題時(shí),Entity Framework將拋出EntityValidationError異常。
MinLength屬性可指定其他屬性驗(yàn)證,就像上面使用的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屬性的值設(shè)置為小于指定的長(zhǎng)度或大于MaxLength屬性中的指定長(zhǎng)度,則EF將拋出EntityValidationError異常。
StringLength還允許指定其他屬性驗(yàn)證,如MaxLength。 不同的是StringLength屬性只能應(yīng)用于Domain類(lèi)的字符串類(lèi)型屬性。參考以下示例代碼 -
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還驗(yàn)證StringLength屬性的屬性值。 現(xiàn)在,如果用戶設(shè)置標(biāo)題(Title),其中包含超過(guò)24個(gè)字符,那么EF將拋出EntityValidationError異常。
默認(rèn)代碼第一個(gè)約定創(chuàng)建一個(gè)與類(lèi)名相同的表名。 如果讓Code First創(chuàng)建數(shù)據(jù)庫(kù),則還可以更改正在創(chuàng)建的表的名稱??梢宰?em>Code First使用現(xiàn)有的數(shù)據(jù)庫(kù)表。 但并不總是這樣,有時(shí)類(lèi)的名稱與數(shù)據(jù)庫(kù)中表的名稱不能總是相匹配。
Table屬性重寫(xiě)此默認(rèn)約定。 對(duì)于給定的域類(lèi),EF Code First將在Table屬性使用指定的名稱來(lái)創(chuàng)建一個(gè)表。
下面來(lái)看看一個(gè)類(lèi)名為Student的例子,按照慣例,Code First假定這將映射到一個(gè)名為Students的表。 如果不是這種情況,可以使用Table屬性指定表的名稱,如以下代碼所示 指定要?jiǎng)?chuàng)建的表名稱為: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; }
}
現(xiàn)在可以看到Table屬性將表指定為StudentsInfo。 生成表時(shí),如下圖所示的表名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模式。 現(xiàn)在,Code First將在Admin模式中創(chuàng)建StudentsInfo表,如以下屏幕截圖所示。
Column屬性也與Table屬性相同,但Table屬性覆蓋表行為,而Column屬性覆蓋列行為。 默認(rèn)代碼第一個(gè)約定創(chuàng)建一個(gè)與屬性名相同的列名。
如果讓Code First創(chuàng)建數(shù)據(jù)庫(kù),并且還希望更改表中列的名稱。Column屬性重寫(xiě)此默認(rèn)約定。 EF Code First將在給定類(lèi)屬性的Column屬性中創(chuàng)建一個(gè)具有指定名稱的列。
下面來(lái)看看下面的例子,其中屬性名為FirstMidName,按照慣例,Code First假定這將映射到一個(gè)名為FirstMidName的列。 如果不是要映射到FirstMidName列時(shí),可以使用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; }
}
現(xiàn)在可以看到Column屬性將列指定為FirstName。 生成表后,可以看到列名為FirstName,如以下屏幕截圖所示。
Index屬性是在Entity Framework 6.1中引入的。
注 - 如果您使用的是早期版本,則本節(jié)中的信息不適用。
可以使用Index屬性在一列或多列上創(chuàng)建索引。將屬性添加到一個(gè)或多個(gè)屬性將導(dǎo)致EF在創(chuàng)建數(shù)據(jù)庫(kù)時(shí)在數(shù)據(jù)庫(kù)中創(chuàng)建相應(yīng)的索引。
在大多數(shù)情況下,索引使數(shù)據(jù)的檢索更快,更高效。但是,使用索引重載表或視圖可能會(huì)不愉快地影響其他操作(如插入或更新)的性能。
索引是實(shí)體框架中的新功能,可以通過(guò)減少?gòu)臄?shù)據(jù)庫(kù)查詢數(shù)據(jù)所需的時(shí)間來(lái)提高Code First應(yīng)用程序的性能。
可以使用Index屬性將索引添加到數(shù)據(jù)庫(kù),并覆蓋默認(rèn)的“唯一”和“群集”設(shè)置,以獲得最適合您的方案的索引。默認(rèn)情況下,索引將被命名為IX_<屬性名稱>
讓我們來(lái)看看以下代碼,其中Index屬性被添加到Course類(lèi)Credits列上。
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屬性應(yīng)用于Credits屬性。 現(xiàn)在,當(dāng)表生成時(shí),將在索引中看到名稱為IX_Credits的索引。
默認(rèn)情況下,索引是非唯一的,但是可以使用IsUnique命名參數(shù)來(lái)指定索引應(yīng)該是唯一的。 以下示例引入了一個(gè)唯一索引,如下面的代碼所示。
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; }
}
Code First約定將處理模型中最常見(jiàn)的關(guān)系。 例如,通過(guò)更改Student類(lèi)中的key屬性名稱,創(chuàng)建了與Enrollment類(lèi)的關(guān)系問(wèn)題。
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; }
}
在生成數(shù)據(jù)庫(kù)時(shí),Code First會(huì)在Enrollment類(lèi)中看到StudentID屬性,并按照約定將其識(shí)別為類(lèi)名稱加ID,作為Student類(lèi)的外鍵。但是Student類(lèi)中沒(méi)有StudentID屬性,而是Student類(lèi)中的StdntID屬性。
解決方法是在Enrollment中創(chuàng)建導(dǎo)航屬性,并使用ForeignKey DataAnnotation來(lái)幫助Code First了解如何構(gòu)建兩個(gè)類(lèi)之間的關(guān)系,如以下代碼所示。
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; }
}
現(xiàn)在可以看到ForeignKey屬性應(yīng)用于導(dǎo)航屬性。
Code First的約定在默認(rèn)情況下,每個(gè)屬于受支持?jǐn)?shù)據(jù)類(lèi)型的屬性都包含getter和setter,它們?cè)跀?shù)據(jù)庫(kù)中表示。 但是在應(yīng)用中并不總是這樣。 NotMapped屬性將覆蓋此默認(rèn)約定。 例如,可能在Student類(lèi)中有一個(gè)屬性,例如FatherName,但不需要存儲(chǔ)它到數(shù)據(jù)庫(kù)表。 那么可以將NotMapped屬性應(yīng)用于您不希望在數(shù)據(jù)庫(kù)中創(chuàng)建列的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屬性應(yīng)用于FatherName屬性。 現(xiàn)在,當(dāng)生成表時(shí),將看到FatherName列不會(huì)在數(shù)據(jù)庫(kù)中創(chuàng)建,但它存在于Student類(lèi)中。
Code First 不會(huì)為沒(méi)有getter或setter的屬性創(chuàng)建一個(gè)列。
在類(lèi)之間有多個(gè)關(guān)系時(shí)使用InverseProperty。 在Enrollment類(lèi)中,可能想要跟蹤注冊(cè)“當(dāng)前課程”的人員和注冊(cè)“以前課程”的人員。
我們?yōu)?code>Enrollment類(lèi)添加兩個(gè)導(dǎo)航屬性。
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類(lèi)。 Course類(lèi)的導(dǎo)航屬性返回到Enrollment類(lèi),其中包含當(dāng)前和以前的所有注冊(cè)。
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; }
}
如果外鍵屬性未包含在上述類(lèi)中所示的特定類(lèi)中,Code First會(huì)創(chuàng)建{Class Name} _ {Primary Key}外鍵列。 生成數(shù)據(jù)庫(kù)表時(shí),您將看到許多外鍵,如以下屏幕截圖所示。

正如所看到的Code First 無(wú)法自己匹配兩個(gè)類(lèi)的屬性。 用于Enrollments的數(shù)據(jù)庫(kù)表應(yīng)該有一個(gè)用于CurrCourse的外鍵和一個(gè)用于PrevCourse的外鍵,但Code First 將創(chuàng)建四個(gè)外鍵屬性,即 -
要解決這些問(wèn)題,可以使用InverseProperty注解來(lái)指定屬性的對(duì)齊方式。
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; }
}
正如上面所看到的那樣,當(dāng)InverseProperty屬性通過(guò)指定它所屬的Enrollment類(lèi)的哪個(gè)引用屬性應(yīng)用于上述Course類(lèi)時(shí),Code First將生成數(shù)據(jù)庫(kù)表,并在Enrollments表中創(chuàng)建兩個(gè)外鍵列,如以下屏幕截圖所示。
我們建議執(zhí)行上述示例以便更好地理解。