Entity Framework - 数据注释

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

Entity Framework依赖于具有用于跟踪实体的键值的每个实体. Code First所依赖的约定之一是它如何暗示哪个属性是每个Code First类中的关键.

  • 约定是寻找一个名为"Id"的属性或一个结合了类名和"Id"的属性,例如"StudentId".

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

  • 学生,课程和注册课程遵循此惯例.

现在让我们假设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资源管理器中查看数据库时,您将看到主键现在是学生中的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; }
}

当您有复合键时,Entity Framework要求您定义键属性的顺序.您可以使用Column批注指定订单.

Column Annotation

时间戳

Code First会将Timestamp属性与ConcurrencyCheck属性相同,但它也会确保代码首次生成的数据库字段不可为空.

  • 使用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 .

ConcurrencyCheck

ConcurrencyCheck注释允许您在用户编辑或删除实体时标记要用于数据库中的并发检查的一个或多个属性.如果您一直在使用EF Designer,那么将设置属性的ConcurrencyMode设置为Fixed.

让我们看一下ConcurrencyCheck如何通过将其添加到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将在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

必需的注释

必需的注释告诉EF需要特定的属性.让我们看看下面的Student类,其中必需的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

MaxLength属性允许您指定其他属性验证.它可以应用于域类的字符串或数组类型属性. EF Code First将设置MaxLength属性中指定的列大小.

让我们看一下MaxLength(24)属性应用于Title属性的以下Course类./p>

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)列标题,如下所示下面的图片.

nvarchar Column

当用户设置标题时包含超过24个字符,然后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; }
}

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

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

实体框架还验证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,如下图所示.

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 schema指定的.现在Code First将在Admin模式中创建StudentsInfo表,如下图所示.

Admin Schema

它也与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,如下图所示.

FirstName

索引

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

  • 您可以在一个上创建索引或者使用IndexAttribute的更多列.

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

  • 在大多数情况下,索引可以更快,更有效地检索数据.但是,使用索引重载表或视图可能会令其不愉快地影响其他操作(如插入或更新)的性能.

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

  • 您可以使用索引向数据库添加索引属性,并覆盖默认的唯一和群集设置,以获得最适合您的方案的索引.

  • 默认情况下,索引将命名为IX_< property name&gt ;

让我们看一下以下代码,其中在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 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属性应用于导航属性.

ForeignKey属性

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属性

Code First不会为属性创建列,该列没有getter或setter,如以下Student类的Address和Age属性示例所示.

InverseProperty

当您在类之间存在多个关系时,将使用InverseProperty.在"注册"课程中,您可能希望跟踪注册当前课程和上一课程的人员.让我们为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首先无法自己匹配两个类中的属性. 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表中只创建两个外键列,如下图所示.

外键列

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