ASP.Net MVC数据注解

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

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

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

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

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

1. 键(Key)

实体框架(Entity Framework或简称为EF )依赖于具有用于跟踪实体的键值的每个实体。 Code First依赖的其中一个约定是它如何暗示哪个属性是每个Code First类中的键。

约定是寻找一个名为Id的属性,或者将类名和Id结合起来的属性,比如StudentId。 该属性将映射到数据库中的主键列。学生,课程和入学课程遵循这个约定。

现在让假设Student类使用名称StdntID而不是ID。 当Code First找不到符合此约定的属性时,它将抛出一个异常,因为Entity Framework要求必须具有一个键属性。

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

下面来看看包含StdntIDStudent类。 它不遵循默认的Code First约定,所以要处理这个问题,添加了Key属性,使StdntID列成为主键。

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

实体框架(Entity Framework)也支持复合键。 复合键是由多个属性组成的主键。例如,有一个DrivingLicense类,其主键是LicenseNumberIssuingCountry的组合。

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注释来指定顺序。

2. 时间戳(Timestamp)

Code First会将Timestamp属性视为ConcurrencyCheck属性,但它也将确保Code First生成的数据库字段不可空。

使用rowversiontimestamp字段进行并发检查更为常见。但是,不要使用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

3. ConcurrencyCheck

ConcurrencyCheck注释允许在用户编辑或删除实体时标记一个或多个要用于数据库并发检查的属性。如果一直在使用EF Designer,那么这将与将属性的“并发性模式”设置为“固定”一致。

下面来看看一个简单的例子,通过将它添加Title属性到Course类中来了解ConcurrencyCheck是如何工作的。

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

4. Required

Required注释告诉实体框架(Entity Framework)需要一个特定的属性。下面来看看Student类,其中必需的id被添加到FirstMidName属性中。 Required属性将强制Entity Framework 确保该属性中包含数据。

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属性应用于FirstMidNameLastName。 因此,Code First将在Students表中创建一个NOT NULLFirstMidNameLastName列,如下图所示。

5. MaxLength

MaxLength属性用于指定其他属性验证。它可以应用于类的字符串或数组类型的属性。 Entity FrameworkCode First 将设置MaxLength属性中指定的列的大小。

下面来看看MaxLength(24)属性应用于Title属性的以下Course类。

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)列标题,如以下屏幕截图所示。

现在当用户设置包含超过24个字符的标题时,Entity Framework将抛出EntityValidationError异常。

6. 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属性的值设置为小于指定的长度或大于MaxLength属性中的指定长度,则EF将抛出EntityValidationError异常。

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

Entity Framework还验证StringLength属性的属性值。 现在,如果用户设置标题(Title),其中包含超过24个字符,那么EF将抛出EntityValidationError异常。

8. Table

默认代码第一个约定创建一个与类名相同的表名。 如果让Code First创建数据库,则还可以更改正在创建的表的名称。可以让Code First使用现有的数据库表。 但并不总是这样,有时类的名称与数据库中表的名称不能总是相匹配。

Table属性重写此默认约定。 对于给定的域类,EF Code First将在Table属性使用指定的名称来创建一个表。

下面来看看一个类名为Student的例子,按照惯例,Code First假定这将映射到一个名为Students的表。 如果不是这种情况,可以使用Table属性指定表的名称,如以下代码所示 指定要创建的表名称为:StudentInfo -

[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。 生成表时,如下图所示的表名StudentInfo

不仅可以指定表名,还可以使用以下代码使用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表,如以下屏幕截图所示。

9. Column

Column属性也与Table属性相同,但Table属性覆盖表行为,而Column属性覆盖列行为。 默认代码第一个约定创建一个与属性名相同的列名。

如果让Code First创建数据库,并且还希望更改表中列的名称。Column属性重写此默认约定。 EF Code First将在给定类属性的Column属性中创建一个具有指定名称的列。

下面来看看下面的例子,其中属性名为FirstMidName,按照惯例,Code First假定这将映射到一个名为FirstMidName的列。 如果不是要映射到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,如以下屏幕截图所示。

10. Index

Index属性是在Entity Framework 6.1中引入的。

注 - 如果您使用的是早期版本,则本节中的信息不适用。

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

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

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

可以使用Index属性将索引添加到数据库,并覆盖默认的“唯一”和“群集”设置,以获得最适合您的方案的索引。默认情况下,索引将被命名为IX_<属性名称>

让我们来看看以下代码,其中Index属性被添加到CourseCredits列上。

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

11. ForeignKey

Code First约定将处理模型中最常见的关系。 例如,通过更改Student类中的key属性名称,创建了与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属性应用于导航属性。

12. NotMapped

Code First的约定在默认情况下,每个属于受支持数据类型的属性都包含gettersetter,它们在数据库中表示。 但是在应用中并不总是这样。 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 不会为没有gettersetter的属性创建一个列。

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

如果外键属性未包含在上述类中所示的特定类中,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表中创建两个外键列,如以下屏幕截图所示。

我们建议执行上述示例以便更好地理解。


上一篇: ASP.Net MVC Razor 下一篇: ASP.Net MVC NuGet包管理