ASP.NET MVC - 数据注解

ASP.NET MVC - 数据注解 首页 / ASP.Net MVC入门教程 / ASP.NET MVC - 数据注解

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

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

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

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

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

Key

约定是寻找一个名为" Id"的属性,或者寻找一个将类名和" Id"组合在一起的属性,如" StudentId",该属性将映射到数据库中的主键列。

现在,假设Student类使用名称StdntID代替ID,当Code First找不到与该约定匹配的属性时,由于Entity Framework要求您必须具有键属性,因此它将引发异常。

您可以使用键注释来指定将哪个属性用作EntityKey。

让我们看一下包含StdntID的Student类。它没有遵循默认的"代码优先"约定,因此要处理此问题,添加了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资源管理器中查看数据库时,您会发现主键现在是"Student"表中的StdntID。

Primary Key StdntID


Entify Frameword还支持组合键,组合键是由多个属性组成的主键,如,您有一个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要求您定义键属性的顺序。

Composite Keys

Timestamp

Code First将把Timestamp属性与ConcurrencyCheck属性相同,但也将确保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。

ConcurrencyCheck

使用ConcurrencyCheck,您可以在用户编辑或删除实体时在数据库中标签一个或多个用于并发检查的属性,如果您使用的是EF Designer,则与将属性的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; }
}

在以上课程类中,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

Required

Required注释告诉EF特定属性是必需的,让我们看一下下面的Student类,其中将Required id添加到FirstMidName属性。

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。因此,代码优先将在学生表中创建一个非空的FirstMidName和LastName列,如以下屏幕截图所示。

Students Table

MaxLength

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

让我们看一下以下课程类,其中将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将在Courseed表中创建nvarchar(24)列Title,如以下屏幕截图所示。

柱 Title Coursed Table

现在,当用户设置包含超过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属性只能应用于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; }
}

Entify Frameword还验证StringLength属性的属性值。现在,如果用户设置标题(包含超过24个字符),则EF将抛出EntityValidationError。

Table

默认代码优先约定创建一个与类名相同的表名,如果要让Code First创建数据库,则还可以更改其创建的表的名称,您可以将Code First与现有数据库一起使用,但是,类的名称与数据库中表的名称并不总是匹配的。

无涯教程网

让我们看一个示例,其中该类名为Student,按照惯惯例,Code First假定它将映射到名为Student的表。如果不是这种情况,则可以使用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 Name 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模式指定的,现在,代码优先将在管理架构中创建StudentsInfo表,如以下屏幕截图所示。

StudentsInfo Table in Admin Schema

Column

它也与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,如以下屏幕截图所示。

柱 Name FirstName

Index

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

您可以使用IndexAttribute在一个或多个列上创建索引,将属性添加到一个或多个属性将使EF在创建数据库时在数据库中创建相应的索引。

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

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

让我们看一下以下代码,其中在“Cours”的“Credits”中添加了“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属性。现在,当生成表时,您将在"Index"中看到IX_Credits。

IX_Credits in Indexes

默认情况下,索引是非唯一的,但是您可以使用 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属性,而在Student类中是StdntID属性。

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属性已应用于导航属性。

外键 Attribute

NotMapped

默认情况下,Code First的约定在数据库中表示具有受支持数据类型的每个属性,其中包括getter和setter,但是,在应用程序中并非总是如此,NotMapped属性将覆盖此默认约定。例如,您可能在Student类中有一个属性,例如FatherName,但是不需要存储它,您可以将NotMapped属性应用于父亲名称属性,而您不想在数据库中创建列。以下是代码。

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

FatherName 柱 Created

Code First不会为没有getter或setter的属性创建列。

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

如果外键属性未包含在上述类中所示的特定类中,则将创建{Class Name} _ {Primary Key}外键列。生成数据库后,您将看到许多外键,如以下屏幕截图所示。

Number of 外键s

如您所见,Code First不能单独匹配两个类中的属性,创建的数据库表应为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表中仅创建两个外键列,如以下屏幕快照所示。

外键 Enrollments Table

我们建议您执行上面的示例以更好地理解。

祝学习愉快!(内容编辑有误?请选中要编辑内容 -> 右键 -> 修改 -> 提交!)

技术教程推荐

Java核心技术面试精讲 -〔杨晓峰〕

快速上手Kotlin开发 -〔张涛〕

深入剖析Kubernetes -〔张磊〕

许式伟的架构课 -〔许式伟〕

检索技术核心20讲 -〔陈东〕

Serverless入门课 -〔蒲松洋(秦粤)〕

操作系统实战45讲 -〔彭东〕

超级访谈:对话张雪峰 -〔张雪峰〕

深入拆解消息队列47讲 -〔许文强〕

好记忆不如烂笔头。留下您的足迹吧 :)