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
。
下面来看看包含StdntID
的Student
类。 它不遵循默认的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
类,其主键是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
注释来指定顺序。
2. 时间戳(Timestamp)
Code First会将Timestamp
属性视为ConcurrencyCheck
属性,但它也将确保Code First生成的数据库字段不可空。
使用rowversion
或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
。
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
属性应用于FirstMidName
和LastName
。 因此,Code First将在Students
表中创建一个NOT NULL
的 FirstMidName
和LastName
列,如下图所示。
5. MaxLength
MaxLength
属性用于指定其他属性验证。它可以应用于类的字符串或数组类型的属性。 Entity Framework的 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将在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
属性被添加到Course
类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
属性应用于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的约定在默认情况下,每个属于受支持数据类型的属性都包含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
类中。
Code First 不会为没有getter
或setter
的属性创建一个列。
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
表中创建两个外键列,如以下屏幕截图所示。
我们建议执行上述示例以便更好地理解。