实体框架 – 数据注释
实体框架 – 数据注释
DataAnnotations 用于配置类,这些类将突出显示最常用的配置。许多 .NET 应用程序也可以理解 DataAnnotations,例如 ASP.NET MVC,它允许这些应用程序利用相同的注释进行客户端验证。DataAnnotation 属性覆盖默认的 CodeFirst 约定。
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 Explorer 中查看您的数据库时,您将看到主键现在是 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 属性相同,但它还会确保代码首先生成的数据库字段不可为空。
-
更常见的是使用 rowversion 或时间戳字段进行并发检查。
-
除了使用 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 将TStamp
在 Courses 表中创建一个时间戳列。
并发检查
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; } }
如上例所示,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 将在 CourseId 表中创建一个 nvarchar(24) 列 Title,如下图所示。
当用户设置包含超过 24 个字符的 Title 时,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 属性只能应用于 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; } }
实体框架还验证 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; } }
您可以在上面的示例中看到,该表是使用 admin 架构指定的。现在 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 Course { 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属性,但是StdntID属性是Student类。
对此的解决方案是在 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 的属性创建列,如以下 Student 类的 Address 和 Age 属性示例所示。
反性质
当类之间有多个关系时使用 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
- 上一个课程_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; } }
正如您所看到的,通过指定它所属的 Enrollment 类的引用属性,在上述 Course 类中应用了 InverseProperty 属性。现在,Code First 将生成一个数据库并在 Enrollments 表中仅创建两个外键列,如下图所示。
我们建议您逐步执行上述示例,以便更好地理解。