ASP.NET MVC – 数据注释
ASP.NET MVC – 数据注释
DataAnnotations 用于配置您的模型类,这将突出显示最常用的配置。许多 .NET 应用程序(例如 ASP.NET MVC)也理解 DataAnnotations,这允许这些应用程序利用相同的注释进行客户端验证。DataAnnotation 属性会覆盖默认的 Code-First 约定。
System.ComponentModel.DataAnnotations包括以下影响列的可为空性或大小的属性。
- 钥匙
- 时间戳
- 并发检查
- 必需的
- 最小长度
- 最长长度
- 字符串长度
System.ComponentModel.DataAnnotations.Schema命名空间包括以下影响数据库架构的属性。
- 桌子
- 柱子
- 指数
- 外键
- 未映射
- 反性质
钥匙
实体框架依赖于每个具有用于跟踪实体的键值的实体。Code First 依赖的约定之一是它如何暗示哪个属性是每个 Code First 类中的键。
约定是查找名为“Id”的属性或将类名和“Id”组合在一起的属性,例如“StudentId”。该属性将映射到数据库中的主键列。Student、Course 和 Enrollment 类都遵循此约定。
现在让我们假设 Student 类使用名称 StdntID 而不是 ID。当 Code First 找不到与此约定匹配的属性时,它将抛出异常,因为 Entity Framework 要求您必须具有键属性。
您可以使用键注释来指定将哪个属性用作 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。
实体框架还支持复合键。复合键是由多个属性组成的主键。例如,您有一个 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 注释来指定顺序。
时间戳
Code First 会将 Timestamp 属性视为 ConcurrencyCheck 属性,但它还会确保 Code First 生成的数据库字段不可为空。
更常见的是使用 rowversion 或时间戳字段进行并发检查。但是,您可以使用更具体的 TimeStamp 注释,而不是使用 ConcurrencyCheck 注释,只要属性的类型是字节数组即可。在给定的类中只能有一个时间戳属性。
让我们通过将 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。
并发检查
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 注释告诉 EF 需要一个特定的属性。让我们看一下下面的 Student 类,其中将 Required id 添加到 FirstMidName 属性。必需属性将强制 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 列,如下面的屏幕截图所示。
最长长度
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,如下面的屏幕截图所示。
现在,当用户设置包含超过 24 个字符的标题时,EF 将抛出 EntityValidationError。
最小长度
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 还允许您指定其他属性验证,如 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 个字符,则 EF 将抛出 EntityValidationError。
桌子
默认代码优先约定创建一个与类名相同的表名。如果您让 Code First 创建数据库,您还可以更改它正在创建的表的名称。您可以将 Code First 与现有数据库一起使用。但是,类的名称并不总是与数据库中表的名称相匹配。
表属性覆盖此默认约定。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 属性使用以下代码为表指定架构。
[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; } }
在上面的例子中,表是用管理模式指定的。现在 Code First 将在 Admin 模式中创建 StudentsInfo 表,如下面的屏幕截图所示。
柱子
它也与 Table 属性相同,但 Table 属性会覆盖表行为,而 Column 属性会覆盖列行为。默认代码优先约定创建与属性名称相同的列名称。
如果您让 Code First 创建数据库,并且您还想更改表中列的名称。列属性覆盖此默认约定。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,如下面的屏幕截图所示。
指数
Index 属性是在 Entity Framework 6.1 中引入的。注意– 如果您使用的是早期版本,则本节中的信息不适用。
您可以使用 IndexAttribute 在一个或多个列上创建索引。将属性添加到一个或多个属性会导致 EF 在创建数据库时在数据库中创建相应的索引。
在大多数情况下,索引使数据检索更快更有效。但是,使用索引重载表或视图可能会令人不快地影响其他操作(例如插入或更新)的性能。
索引是实体框架中的新功能,您可以通过减少从数据库查询数据所需的时间来提高 Code First 应用程序的性能。
您可以使用 Index 属性向数据库添加索引,并覆盖默认的 Unique 和 Clustered 设置以获得最适合您的方案的索引。默认情况下,索引将命名为 IX_<property name>
让我们看一下下面的代码,其中为 Credits 在 Course 类中添加了 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 属性。现在生成表时,您将在索引中看到 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; } }
外键
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 属性应用于导航属性。
未映射
默认情况下,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 的属性创建列。
反性质
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 表中创建两个外键列,如下面的屏幕截图所示。
我们建议您执行上述示例以更好地理解。