实体框架 - 数据注解



DataAnnotations 用于配置类,这些类将突出显示最常用的配置。DataAnnotations 也被许多 .NET 应用程序(如 ASP.NET MVC)理解,这允许这些应用程序利用相同的注解进行客户端验证。DataAnnotation 属性会覆盖默认的 CodeFirst 约定。

System.ComponentModel.DataAnnotations 包括以下影响列的可空性或大小的属性。

  • Key
  • Timestamp
  • ConcurrencyCheck
  • Required
  • MinLength
  • MaxLength
  • StringLength

System.ComponentModel.DataAnnotations.Schema 命名空间包含以下影响数据库模式的属性。

  • Table
  • Column
  • Index
  • ForeignKey
  • NotMapped
  • InverseProperty

Key

实体框架依赖于每个实体都具有一个键值,它用于跟踪实体。Code First 依赖的约定之一是如何暗示每个 Code First 类中的哪个属性是键。

  • 约定是查找名为“Id”的属性或将类名和“Id”组合在一起的属性,例如“StudentId”。

  • 该属性将映射到数据库中的主键列。

  • Student、Course 和 Enrollment 类遵循此约定。

现在假设 Student 类使用 StdntID 而不是 ID。当 Code First 找不到与该约定匹配的属性时,由于实体框架要求您必须具有键属性,因此它将抛出异常。您可以使用 key 注解来指定哪个属性用作 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。

Primary Key

实体框架也支持复合键。复合键也是主键,由多个属性组成。例如,您有一个 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 注解来指定顺序。

Column Annotation

Timestamp

Code First 将以与 ConcurrencyCheck 属性相同的方式处理 Timestamp 属性,但它还将确保 Code First 生成的数据库字段不可为空。

  • 更常见的是使用 rowversion 或 timestamp 字段进行并发检查。

  • 您可以使用更具体的 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 的 timestamp 列。

ConcurrencyCheck

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 注解

Required 注解告诉 EF 特定属性是必需的。让我们看一下以下 Student 类,其中 Required id 添加到 FirstMidName 属性中。Required 属性将强制 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 列,如下面的图像所示。

Not Null

MaxLength

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 表中创建一个名为 Title 的 nvarchar(24) 列,如下面的图像所示。

nvarchar Column

当用户设置包含超过 24 个字符的 Title 时,EF 将抛出 EntityValidationError。

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; }
}

如果将 Title 属性的值设置为小于 MinLength 属性中指定长度或大于 MaxLength 属性中指定长度的值,则 EF 将抛出 EntityValidationError。

StringLength

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 个字符的 Title,则 EF 将抛出 EntityValidationError。

Table

默认的 Code First 约定创建与类名类似的表名。如果您让 Code First 创建数据库,并且还希望更改它正在创建的表名。然后 -

  • 您可以将 Code First 与现有数据库一起使用。但并非总是类名与数据库中表名匹配的情况。

  • Table 属性覆盖此默认约定。

  • 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,如下面的图像所示。

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 表,如下面的图像所示。

Admin Schema

Column

它也与 Table 属性相同,但 Table 属性覆盖表行为,而 Column 属性覆盖列行为。默认的 Code First 约定创建与属性名类似的列名。如果您让 Code First 创建数据库,并且还希望更改表中的列名。然后 -

  • Column 属性覆盖默认约定。

  • 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,如下面的图像所示。

FirstName

Index

Index 属性是在 Entity Framework 6.1 中引入的。如果您使用的是早期版本,则本节中的信息不适用。

  • 您可以使用 IndexAttribute 在一个或多个列上创建索引。

  • 将属性添加到一个或多个属性将导致 EF 在创建数据库时在数据库中创建相应的索引。

  • 在大多数情况下,索引可以使数据检索更快更高效。但是,在表或视图上使用过多的索引可能会对其他操作(如插入或更新)的性能产生负面影响。

  • 索引是 Entity Framework 中的新功能,您可以通过减少从数据库查询数据所需的时间来提高 Code First 应用程序的性能。

  • 您可以使用 Index 属性向数据库添加索引,并覆盖默认的 Unique 和 Clustered 设置,以获得最适合您方案的索引。

  • 默认情况下,索引将命名为 IX_<属性名称>

让我们看一下下面的代码,其中在 Course 类中为 Credits 添加了 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。

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; }
}

ForeignKey

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 数据注释来帮助 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 属性已应用于导航属性。

ForeignKey Attribute

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 类中。

NotMapped Attribute

对于没有 getter 或 setter 的属性,Code First 不会创建列,如下面的 Student 类 Address 和 Age 属性示例所示。

InverseProperty

当您在类之间有多个关系时,使用 InverseProperty。在 Enrollment 类中,您可能希望跟踪谁注册了 Current Course 和 Previous Course。让我们为 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 会创建 {类名} _{主键} 外键列,如上述类所示。生成数据库时,您将看到以下外键。

Foreign Keys

如您所见,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; }
}

如您所见,通过指定它属于 Enrollment 类的哪个引用属性,在上面的 Course 类中应用了 InverseProperty 属性。现在,Code First 将生成一个数据库并在 Enrollments 表中仅创建两个外键列,如下面的图像所示。

Foreign Key Columns

我们建议您逐步执行以上示例,以便更好地理解。

广告

© . All rights reserved.